简体   繁体   English

使用 Generics 检索 Generic 类型的类列表

[英]Using Generics to retrieve a list of classes of type Generic

I'm relatively new to generics in Java, so I apologize if this is something common that gets taught in schools (I'm pretty much self-taught).我对 Java 中的 generics 比较陌生,所以如果这是在学校教授的常见内容,我很抱歉(我几乎是自学的)。 Let's say I have the interface and abstract class below假设我有下面的接口和抽象 class

public interface IChallenge<T> {
    boolean handle(T e);
    Class<? extends T> getType();
}
public abstract class AbstractChallenge<T> implements IChallenge<T> {
    protected Class<T> clazz;
    
    @Override
    public Class<? extends T> getType() {
        return this.clazz;
    }
}

For every class that extends AbstractChallenge, the handle method takes in the parameter that is specified for the generic.对于每个扩展 AbstractChallenge 的 class, handle方法接受为泛型指定的参数。 So if I had an event class that gets triggered when Event happens, I would have因此,如果我有一个在Event发生时触发的事件 class,我会

public class EventChallenge extends AbstractChallenge<Event> {
    public EventChallenge() {
        super(Event.class);
    }

    @Override
    public boolean handle(Event e) {}
}

My problem comes when I'm trying to pass a specific class to the handle method.当我尝试将特定的 class 传递给handle方法时,我的问题就出现了。 Since the generic can be any class, and there can be multiple challenges with the same type, I have the challenges stored in a map with their type as the key.由于泛型可以是任何 class,并且同一类型可能存在多个挑战,因此我将挑战存储在 map 中,其类型为键。

private Map<Something, List<AbstractChallenge<Something>> challenges = new HashMap<>();

With the ultimate hope of achieving something along the lines of最终希望实现以下目标

List<AbstractChallenge<A>> specificChallenges = this.challenges.get(A.class);
specificChallenges.removeIf(challenge -> challenge.handle(A));

But I'm having a hard time figuring out what goes in the 'Something'.但是我很难弄清楚“某事”中的内容。 If I put the wildcard ?如果我放通配符? symbol, then IntelliJ says that handle must take in a parameter of the requirement: capture of?符号,然后 IntelliJ 说handle必须接受要求的参数:捕获? when I pass it class A. The best I've gotten to is to not specify the type for AbstractChallenge but I'd like a better solution.当我通过它时 class A. 我得到的最好的方法是不指定AbstractChallenge的类型,但我想要一个更好的解决方案。

Any ideas?有任何想法吗? Thanks!谢谢!

What you seek is something like that (I took the comment here):您所寻求的是类似的东西(我在这里发表了评论):

private Map<Class<?>, List<IChallenge<?>> challenges = new HashMap<>();

A a = ...;
challenges.get(a.getClass())
          .removeIf(challenger -> challenger.handle(a));

This is unsafe as it can be and you can't do much because you don't know the actual type of T (the compiler does not, so the much it can do is infer it, and in this case, the type would be Object ):这是不安全的,因为你不知道 T 的实际类型(编译器不知道,所以它可以做的就是推断它,在这种情况下,类型将是Object ):

  • The key can be any type, for example Integer.class密钥可以是任何类型,例如Integer.class
  • The value can be any type IChallenge<T> and if T is not Integer (or Number , Object , eg: any type in the hierarchy of T ), it may fail if the implementation use the object it handles and do some cast.该值可以是任何类型IChallenge<T>并且如果T不是Integer (或NumberObject ,例如: T层次结构中的任何类型),它可能会失败,如果实现使用 ZA8CFDE63311BD59EB266AC96B 它处理8

When you add:添加时:

challenges.get(Integer.class).add((Number a) -> a.intValue() > 10); // #1
challenges.get(Integer.class).add((Integer a) -> a.intValue() > 10); // #2
challenges.get(Integer.class).add((Object a) -> a != null); // #3
challenges.get(Integer.class).add((String a) -> a.length() > 10); // #4

Here is an example:这是一个例子:

Integer a = Integer.valueOf(5);
// #1 -> ok: a is a Number
challenges.get(a.getClass()).removeIf(c -> c.handle(a));
// #2 -> ok: a is an Integer
challenges.get(a.getClass()).removeIf(c -> c.handle(a));
// #3 -> ok: a is an Object
challenges.get(a.getClass()).removeIf(c -> c.handle(a));
// #4 ->ko: a is not a String
challenges.get(a.getClass()).removeIf(c -> c.handle(a));

If you wish to avoid that, but still be able to handle anything challenge, you should ensure that the class holding/building the challenges do it correctly:如果您希望避免这种情况,但仍然能够应对任何挑战,则应确保持有/构建挑战的 class 正确完成:

public <T> void addChallenge(Class<T> type, IChallenge<T> challenge) {
  challenges.computeIfAbsent(type, ignored -> new ArrayList<>()).add(challenge);
}

While you could use the getType() your defined in IChallenge , I wanted to show you how to enforce that the type (the key) and the IChallenge (the value) can be secured: normally, unless you gave write access to the map to other classes, this should be safe because the compiler validate the type at insertion.虽然您可以使用您在IChallenge中定义的getType() ,但我想向您展示如何强制保护类型(键)和IChallenge (值):通常,除非您授予对 map 的写入权限其他类,这应该是安全的,因为编译器会在插入时验证类型。

Therefore, when you remove them, you should never have a ClassCastException due to the type parameter of IChallenge .因此,当您删除它们时,由于IChallenge的类型参数,您永远不应该有ClassCastException

You could also try playing with ? super T你也可以试试玩? super T ? super T and ? extends T ? super T? extends T ? extends T but that's another challenge. ? extends T但这是另一个挑战。

-- --

Regarding your comment:关于您的评论:

I'm not entirely sure how to go about using the addChallenge method you specified.我不完全确定如何使用您指定的 addChallenge 方法。 Right now, I have a list of Class> for every challenge created, and when a specific challenge should be loaded, the program instantiates using.newInstance().现在,对于创建的每个挑战,我都有一个 Class> 列表,当应该加载特定挑战时,程序使用.newInstance() 进行实例化。 Should I be doing it differently?我应该采取不同的做法吗? I only need a certain amount of challenges loaded at once, not all – DeprecatedFrank我只需要一次加载一定数量的挑战,而不是全部——DeprecatedFrank

I am not telling to load all challenges at once, I am merely telling your to use OOP to ensure that no one, but your challenge holder (let call it ChallengeHolder ) manage the map, and manage it so that you avoid generics pitfall:我不是要立即加载所有挑战,我只是告诉您使用 OOP 来确保没有人,但是您的挑战持有者(我们称之为ChallengeHolder )管理 map,并对其进行管理,以便您避免 Z56B97998B338B9F37EEZFA 陷阱:846B9F370FF5

class ChallengeHolder {
  private final Map<Class<?>, List<IChallenge<?>>> challenges;
  public ChallengeHolder() {
    this.challenges = new HashMap<>();
  }

  public <T> void addChallenge(Class<T> type, IChallenge<T> challenge) {      
      challenges.computeIfAbsent(type, ignored -> new ArrayList<>()).add(challenge);
  }

  public boolean handle(Object a) {
    List<IChallenge<T>> challengers = challenges.get(a);
    if (challengers == null) return false;
    return challengers.removeIf(c -> c.handle(a));
  }   

}

Since there are no public access to challenges beyond what the ChallengeHolder class provides, there should be no problem with using Object or Class<T> .由于除了ChallengeHolder class 提供的挑战之外,没有公共访问权限,因此使用ObjectClass<T>应该没有问题。

If you need to create IChallenge on demand, then you could perhaps an implementation like this:如果您需要按需创建IChallenge ,那么您也许可以像这样实现:

public class LazyChallenge<T> implements IChallenge<T> {
  private final Class<IChallenge<T>> impl;
  private IChallenge<T> value;

  public LazyChallenge(IChallenge<T> impl) {
    this.impl = impl;
  }

  public boolean handle(T o) {
    if (value == null) {
      try {
        value = impl.getConstructor().newInstance();
      } catch (java.lang.ReflectiveOperationException e) { // ... a bunch of exception your IDE will fill in ...
        throw new IllegalStateException(e);
      }
    }
    return value.handle(o);
  }
}

You would then add it to ChallengeHolder :然后,您将其添加到ChallengeHolder

challengeHolder.addChallenge(String.class, new LazyChallenge<>(StringChallenge.class));

Or you could use lambda to avoid the reflection:或者您可以使用 lambda 来避免反射:

public class LazyChallenge<T> implements IChallenge<T> {
  private final Class<IChallenge<T>> supplier;
  private IChallenge<T> value;

  public LazyChallenge(Supplier<IChallenge<T>> supplier) {
    this.supplier = supplier;
  }

  public boolean handle(T o) {
    if (value == null) {
      value = supplier.get();
    }
    return value.handle(o);
  }
}

And:和:

challengeHolder.addChallenge(String.class, new LazyChallenge<>(StringChallenge::new));

And after though, you may directly use Supplier in place of IChallenge in ChallengeHolder :之后,您可以直接在ChallengeHolder中使用Supplier代替IChallenge

class ChallengeHolder {
  private final Map<Class<?>, List<Supplier<IChallenge<?>>>> challenges;
  public ChallengeHolder() {
    this.challenges = new HashMap<>();
  }

  public <T> void addChallenge(Class<T> type, Supplier<IChallenge<T>> challenge) {      
      challenges.computeIfAbsent(type, ignored -> new ArrayList<>()).add(challenge);
  }

  public boolean handle(Object a) {
    List<IChallenge<T>> challengers = challenges.get(a);
    if (challengers == null) return false;
    return challengers.removeIf(c -> c.get().handle(a));
  }   

}

StringChallenge existing = ... ;
// always reuse an existing
challengeHolder.addChallenge(String.class, () -> existing);
// bring a new challenge each time that ChallengeHolder::handle is called
challengeHolder.addChallenge(String.class, StringChallenge::new);

If I were to implements it, I would use the lambda way because you avoid reflection pitfalls (the try catch, the visibility problems especially given that Java 9++ introduced modules, ...).如果我要实现它,我会使用 lambda 方式,因为您可以避免反射陷阱(try catch,可见性问题,特别是考虑到 Java 9++ 引入了模块,...)。

The LazyChallenge defined above may help to avoid creating the StringChallenge more than one.上面定义的LazyChallenge可能有助于避免创建多个StringChallenge In that case, it would be best to have it implements Supplier<T> instead of IChallenge<T> .在这种情况下,最好让它实现Supplier<T>而不是IChallenge<T>

This whole digression does not change what I pointed out earlier: ensure that only ChallengeHolder read/write the map.这整个题外话并没有改变我之前指出的:确保只有ChallengeHolder读/写 map。

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

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