简体   繁体   English

Java按泛型类型获取集合

[英]Java get collection by generic type

Let's say I have my class Context like this. 假设我有类似这样的类Context

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

Then I have a generic class Performer , that wants to perform operations based on the generic type from the Context class, in the following way - not possible in Java. 然后我有一个泛型类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); 
   }
}

Then, if I say something like saveToSet(B b) , the right set from the Context will be called, in this case sB (Set<B>) and the new instance will be added to that set. 然后,如果我说类似saveToSet(B b) ,将调用Context的右集,在本例中为sB (Set<B>) ,新实例将被添加到该集合中。

Ok, the question... How do to this in Java? 好的,问题......在Java中怎么做? :D :d

Sadly such an approach will not work in Java due to type erasure . 遗憾的是,由于类型擦除,这种方法在Java中不起作用。

Essentially all the runtime will see is Context.Set<java.lang.Object>.add(t); 基本上所有运行时都会看到Context.Set<java.lang.Object>.add(t); and the collapse of your fields to Set s of java.lang.Object s, and so it will be unable to disambiguate. 并且你的字段崩溃为java.lang.ObjectSet ,因此它将无法消除歧义。

You could work around this by writing overloads like saveToSet(A a) , saveToSet(B b) etc. 您可以通过编写saveToSet(A a)saveToSet(B b)等重载来解决此问题。

In many ways, including this one, Java generics are the poor cousin of C++ templates. 在很多方面,包括这个,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;
}

This will not work due to a concept in Java called as type erasure . 由于Java中称为类型擦除的概念,这将无法工作。 Basically, the generic type is erased at runtime, and the runtime will not know which type of object it was or is. 基本上,泛型类型在运行时被擦除,并且运行时将不知道它是哪种类型的对象。 So no, you cannot do something like that. 所以不,你不能做那样的事情。

You can, however, make separate methods, or you can make a inherited classes and implement specific logic in that overriden method. 但是,您可以创建单独的方法,或者可以在该重写方法中创建继承的类并实现特定的逻辑。

One workaround for Java is using enriched types. Java的一种解决方法是使用丰富的类型。 Those are basically types that hold their own generic parameters as Class<T> attributes. 这些基本上是将自己的通用参数保存为Class<T>属性的类型。 So, instead of using a standard Set , use your own implementation that stores the class. 因此,使用您自己的存储类的实现,而不是使用标准Set Something like: 就像是:

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

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

maybe you could use such approach. 也许你可以使用这种方法。

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

other possible way to nail it is named injection and method interception with Guice (minus: low performance) and there are lots of optimizations needed to be done to this code ( class type handling, may be MapMultibinding from Guice to inject datasets ) 其他可能的方法是使用Guice命名注入和方法拦截(减去:低性能)并且需要对此代码进行大量优化(类类型处理,可能是从Guice MapPultibinding到注入datasets

Actually you can achieve your goal and bypass type erasure using reflection. 实际上,您可以使用反射实现目标并绕过类型擦除。 But only if your Context has fields with explicitly defined generic type parameters. 但前提是您的Context具有明确定义的泛型类型参数的字段。

So if you are not afraid of performance penalty then reflection is your friend. 因此,如果你不害怕性能损失,那么反思就是你的朋友。 You can reflect type parameter of each Set field like this: 您可以像这样反映每个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);
}

After that you can use obtained type info to implement your saveToSet method 之后,您可以使用获取的类型信息来实现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