Java按泛型类型获取集合

时间:2016-08-17 10:21:24

标签: java generics

假设我的班级Context是这样的。

public class Context {
    Set<A> sA;
    Set<B> sB;
    Set<C> sC;
}

然后我有一个泛型类Performer,它希望基于Context类中的泛型类型以下列方式执行操作 - 在Java中是不可能的。

public class Performer<T> { // T can be type of A,B or C

   public void saveToSet(T t) {
       Context.Set<T>.add(t); 
   }
}

然后,如果我说saveToSet(B b)之类的内容,则会调用Context中的右侧集合,在这种情况下为sB (Set<B>),新实例将添加到该集合中。< / p>

好的,问题是......在Java中怎么做? :d

6 个答案:

答案 0 :(得分:7)

遗憾的是,由于类型擦除,这种方法在Java中不起作用。

基本上所有运行时都会看到while($line = mysqli_fetch_assoc($result)){ // rest of the code } 并且您的字段已经崩溃到Context.Set<java.lang.Object>.add(t);的{​​{1}},因此无法消除歧义。

您可以通过编写Setjava.lang.Object等重载来解决此问题。

在很多方面,包括这个,Java泛型都是C ++模板的表兄弟。

答案 1 :(得分:0)

//Not very ice, but something like this can be useful

if (x instanceof A) {
    sA.add((A)x);
    return;
}

if (x instanceof B) {
    sB.add((B)x);
    return;
}

答案 2 :(得分:0)

由于Java中称为 type erasure 的概念,这将无法工作。基本上,泛型类型在运行时被擦除,并且运行时将不知道它是哪种类型的对象。所以不,你不能做那样的事情。

但是,您可以创建单独的方法,或者可以在该重写方法中创建继承的类并实现特定的逻辑。

答案 3 :(得分:0)

Java的一种解决方法是使用丰富的类型。这些基本上是将自己的通用参数保存为Class<T>属性的类型。因此,使用您自己的存储类的实现,而不是使用标准Set。类似的东西:

class TypeStoringSet<T> extends HashSet<T> {
     private Class<T> clazz;
     ... getter and setter here

     public TypeStoringSet<T>(Class<T> clazz) {
          super();
          this.clazz = clazz;
     }
}

答案 4 :(得分:0)

也许你可以使用这种方法。

class Context {
    Map<String,Set<Object>> datasets = new HashMap<>();

    public <T> void add(T data){
        final String name = data.getClass().getName();
        if(!datasets.containsKey(name)) datasets.put(name, new HashSet<>());
        datasets.get(name).add(data);
    }

    public <T> Set<T> get(Class<T> type){
        return (Set<T>) datasets.get(type.getName());
    }
}

class Performer<T> { // T can be ANY type

    public void saveToSet(T t) {
        final Context context = new Context();
        context.add(t);
    }
}

    final Set<A> aSet = context.get(A.class);
    final Set<B> bSet = context.get(B.class);
    final Set<C> cSet = context.get(C.class);

其他可能的指甲方法是使用Guice命名注入和方法拦截(减:低性能) 并且需要对此代码进行大量优化(类类型处理,可能是从Guice MapMultibinding注入datasets

答案 5 :(得分:0)

实际上,您可以使用反射实现目标并绕过类型擦除。但仅当您的Context具有明确定义的泛型类型参数的字段时。

因此,如果你不害怕性能损失,那么反思就是你的朋友。您可以像这样反映每个Set字段的类型参数:

// you can fill that lookup map upon Producer creation
Map<Type, Field> lookup = new HashMap<>();
for (Field field : Context.class.getDeclaredFields()) {
    // this code relies on fact that Set has only one
    // type argument which is not parametrized
    // also you need of course do some sanity checks
    // (i.e field is a type of Set, etc)

    Type setType = field.getGenericType();
    Type setGenericArgument =
       ((ParameterizedType) superClass).getActualTypeArguments()[0];

    field.setAccisseble(true);
    lookup.put(setGenericArgument, field);
}

之后,您可以使用获取的类型信息来实施saveToSet方法

public void saveToSet(T t) {
    // also do exception processing and lookup 
    // map checks here
    Field field = lookup.get(t.getClass());
    ((Set<T>) field.get(context)).add(t); 
}