混合泛型类型变量以在Java中实现类型安全的映射函数

时间:2021-02-13 16:01:04

I want to write a type-safe map method in Java that returns a Collection of the same type as the argument passed (i.e. ArrayList, LinkedList, TreeSet, etc.) but with a different generic type (that between the angled brackets), determined by the generic type of another parameter (the resulting type of the generic mapping function).

我想在Java中编写一个类型安全的map方法,它返回一个与传递的参数相同类型的Collection(即ArrayList,LinkedList,TreeSet等)但是具有不同的泛型类型(在有角度的括号之间),确定通过另一个参数的泛型类型(通用映射函数的结果类型)。

So the code would be used as:

所以代码将用作:

public interface TransformFunctor<S, R> {
    public R apply(S sourceObject);
}

TransformFunctor i2s = new TransformFunctor<Integer, String>() {
    String apply(Integer i) {return i.toString();};

ArrayList<Integer> ali = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4));
ArrayList<String> als = map(ali, i2s);

TreeSet<Integer> tsi = new TreeSet<Integer>(Arrays.asList(1, 2, 3, 4));
TreeSet<String> tss = map(tsi, i2s);

The idea would be something like:

这个想法是这样的:

public static <C extends Collection<?>, S, R>
C<R> map(final C<S> collection, final TransformFunctor<S, R> f)
throws Exception {
    //if this casting can be removed, the better
    C<R> result = (C<R>) collection.getClass().newInstance();
    for (S i : collection) {
        result.add(f.apply(i));
    }
    return result;
}

but that doesn't work because the compiler isn't expecting generic type variables to be further specialised (I think).

但这不起作用,因为编译器不希望泛型类型变量进一步专门化(我认为)。

Any idea on how to make this work?

有关如何使这项工作的任何想法?

4 个解决方案

#1


2  

AFAIK there is no way to do this in Java so that it's both (a) compile-type-safe and (b) the caller of map does not need to repeat the collection type of source and target. Java does not support higher-order kinds, what you're asking for can be achieved in Scala 2.8, but even there the implementation details are somewhat complex, see this SO question.

AFAIK在Java中无法做到这一点,因此它既是(a)编译类型安全的,又是(b)map的调用者不需要重复源和目标的集合类型。 Java不支持高阶类,在Scala 2.8中可以实现您所要求的,但即使在那里,实现细节也有些复杂,请参阅此SO问题。

#2


1  

It seems that it is not possible to use generic types on generic types. Since you need only a limited number of those you can just enumerate them:

似乎无法在泛型类型上使用泛型类型。由于您只需要有限数量的那些,您可以枚举它们:

public static <CS extends Collection<S>, CR extends Collection<R>, S, R> CR map(
        final CS collection, final TransformFunctor<S, R> f)
        throws Exception {
    // if this casting can be removed, the better
    CR result = (CR) collection.getClass().newInstance();
    for (S i : collection) {
        result.add(f.apply(i));
    }
    return result;
}

I used CS as source collection and CR as result collection. I'm afraid you can't remove the cast because you can't use generics at runtime. newInstance() just creates an object of type some collection of Object and the cast to CR is necessary to satisfy the compiler. But it's still something of a cheat. That's why the compiler issues a warning that you have to suppress with @SuppressWarnings("unchecked").

我使用CS作为源集合,CR作为结果集合。我担心你不能删除演员,因为你不能在运行时使用泛型。 newInstance()只是创建一个Object类型的对象,并且为了满足编译器,必须转换为CR。但它仍然是一种骗局。这就是编译器发出警告的原因,你必须用@SuppressWarnings(“unchecked”)来抑制它。

Interesting question btw.

有趣的问题顺便说一句。

#3


0  

This works:

class Test {

    public static void main(String[] args) throws Exception {
        Function<Integer, String> i2s = new Function<Integer, String>() {
            public String apply(Integer i) {
                return i.toString();
            }
        };

        ArrayList<Integer> ali = new ArrayList<Integer>(Arrays.asList(1, 2, 3,
                4));
        ArrayList<String> als = map(ali, i2s);

        TreeSet<Integer> tsi = new TreeSet<Integer>(Arrays.asList(1, 2, 3, 4));
        TreeSet<String> tss = map(tsi, i2s);
        System.out.println(""+ali+als+tss);
    }

    static <SC extends Collection<S>, S, T, TC extends Collection<T>> TC map(
            SC collection, Function<S, T> func) throws Exception {
        // if this casting can be removed, the better
        TC result = (TC) collection.getClass().newInstance();
        for (S i : collection) {
            result.add(func.apply(i));
        }
        return result;
    }

}

interface Function<S, R> {
    R apply(S src);
}

#4


0  

What you want to do is not possible. You can, however, specify the generic type of your returned collection.

你想做什么是不可能的。但是,您可以指定返回集合的泛型类型。

public static <S, R> Collection<R> map(Collection<S> collection, TransformFunctor<S,R> f) throws Exception {
    Collection<R> result = collection.getClass().newInstance();
    for (S i : collection) {
        result.add(f.apply(i));
    }
    return result;
}

This does return a collection of the type you specify with your argument, it just doesn't do so explicitly. It is, however, safe to cast the method return to the collection type of your argument. You should note, however, that the code will fail if the collection type you pass does not have a no-arg constructor.

这会返回您使用参数指定的类型的集合,它不会显式地这样做。但是,将方法返回转换为参数的集合类型是安全的。但是,您应该注意,如果您传递的集合类型没有no-arg构造函数,则代码将失败。

You can then use it like this:

然后你可以像这样使用它:

ArrayList<Integer> ali = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4));
ArrayList<String> als = (ArrayList<String>) map(ali, i2s);

TreeSet<Integer> tsi = new TreeSet<Integer>(Arrays.asList(1, 2, 3, 4));
TreeSet<String> tss = (TreeSet<String>) map(tsi, i2s);

#1


2  

AFAIK there is no way to do this in Java so that it's both (a) compile-type-safe and (b) the caller of map does not need to repeat the collection type of source and target. Java does not support higher-order kinds, what you're asking for can be achieved in Scala 2.8, but even there the implementation details are somewhat complex, see this SO question.

AFAIK在Java中无法做到这一点,因此它既是(a)编译类型安全的,又是(b)map的调用者不需要重复源和目标的集合类型。 Java不支持高阶类,在Scala 2.8中可以实现您所要求的,但即使在那里,实现细节也有些复杂,请参阅此SO问题。

#2


1  

It seems that it is not possible to use generic types on generic types. Since you need only a limited number of those you can just enumerate them:

似乎无法在泛型类型上使用泛型类型。由于您只需要有限数量的那些,您可以枚举它们:

public static <CS extends Collection<S>, CR extends Collection<R>, S, R> CR map(
        final CS collection, final TransformFunctor<S, R> f)
        throws Exception {
    // if this casting can be removed, the better
    CR result = (CR) collection.getClass().newInstance();
    for (S i : collection) {
        result.add(f.apply(i));
    }
    return result;
}

I used CS as source collection and CR as result collection. I'm afraid you can't remove the cast because you can't use generics at runtime. newInstance() just creates an object of type some collection of Object and the cast to CR is necessary to satisfy the compiler. But it's still something of a cheat. That's why the compiler issues a warning that you have to suppress with @SuppressWarnings("unchecked").

我使用CS作为源集合,CR作为结果集合。我担心你不能删除演员,因为你不能在运行时使用泛型。 newInstance()只是创建一个Object类型的对象,并且为了满足编译器,必须转换为CR。但它仍然是一种骗局。这就是编译器发出警告的原因,你必须用@SuppressWarnings(“unchecked”)来抑制它。

Interesting question btw.

有趣的问题顺便说一句。

#3


0  

This works:

class Test {

    public static void main(String[] args) throws Exception {
        Function<Integer, String> i2s = new Function<Integer, String>() {
            public String apply(Integer i) {
                return i.toString();
            }
        };

        ArrayList<Integer> ali = new ArrayList<Integer>(Arrays.asList(1, 2, 3,
                4));
        ArrayList<String> als = map(ali, i2s);

        TreeSet<Integer> tsi = new TreeSet<Integer>(Arrays.asList(1, 2, 3, 4));
        TreeSet<String> tss = map(tsi, i2s);
        System.out.println(""+ali+als+tss);
    }

    static <SC extends Collection<S>, S, T, TC extends Collection<T>> TC map(
            SC collection, Function<S, T> func) throws Exception {
        // if this casting can be removed, the better
        TC result = (TC) collection.getClass().newInstance();
        for (S i : collection) {
            result.add(func.apply(i));
        }
        return result;
    }

}

interface Function<S, R> {
    R apply(S src);
}

#4


0  

What you want to do is not possible. You can, however, specify the generic type of your returned collection.

你想做什么是不可能的。但是,您可以指定返回集合的泛型类型。

public static <S, R> Collection<R> map(Collection<S> collection, TransformFunctor<S,R> f) throws Exception {
    Collection<R> result = collection.getClass().newInstance();
    for (S i : collection) {
        result.add(f.apply(i));
    }
    return result;
}

This does return a collection of the type you specify with your argument, it just doesn't do so explicitly. It is, however, safe to cast the method return to the collection type of your argument. You should note, however, that the code will fail if the collection type you pass does not have a no-arg constructor.

这会返回您使用参数指定的类型的集合,它不会显式地这样做。但是,将方法返回转换为参数的集合类型是安全的。但是,您应该注意,如果您传递的集合类型没有no-arg构造函数,则代码将失败。

You can then use it like this:

然后你可以像这样使用它:

ArrayList<Integer> ali = new ArrayList<Integer>(Arrays.asList(1, 2, 3, 4));
ArrayList<String> als = (ArrayList<String>) map(ali, i2s);

TreeSet<Integer> tsi = new TreeSet<Integer>(Arrays.asList(1, 2, 3, 4));
TreeSet<String> tss = (TreeSet<String>) map(tsi, i2s);