简体   繁体   English

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

[英]mix generic type variables to implement a type-safe map function in Java

I want to write a type-safe map method in Java that returns a Collection of the same type as the argument passed (ie 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? 有关如何使这项工作的任何想法?

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

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. 我使用CS作为源集合, CR作为结果集合。 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. newInstance()只是创建一个Object类型的对象,并且为了满足编译器,需要转换为CR But it's still something of a cheat. 但它仍然是一种欺骗。 That's why the compiler issues a warning that you have to suppress with @SuppressWarnings("unchecked") . 这就是编译器发出警告的原因,你必须使用@SuppressWarnings("unchecked")来抑制。

Interesting question btw. 有趣的问题btw。

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);

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);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM