繁体   English   中英

Java按泛型类型获取集合

[英]Java get collection by generic type

假设我有类似这样的类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>) ,新实例将被添加到该集合中。

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

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

基本上所有运行时都会看到Context.Set<java.lang.Object>.add(t); 并且你的字段崩溃为java.lang.ObjectSet ,因此它将无法消除歧义。

您可以通过编写saveToSet(A a)saveToSet(B b)等重载来解决此问题。

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

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

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

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

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

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

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 MapPultibinding到注入datasets

实际上,您可以使用反射实现目标并绕过类型擦除。 但前提是您的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); 
}

暂无
暂无

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

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