使用泛型检索泛型类型的类的列表



我对Java中的泛型相对陌生,所以如果这是学校里常见的东西,我很抱歉(我几乎是自学成才的(。假设我有下面的接口和抽象类

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

对于每个扩展AbstractChallenge的类,handle方法接受为泛型指定的参数。因此,如果我有一个在Event发生时触发的事件类,我就会有

public class EventChallenge extends AbstractChallenge<Event> {
public EventChallenge() {
super(Event.class);
}
@Override
public boolean handle(Event e) {}
}

当我试图将一个特定的类传递给handle方法时,问题就来了。由于泛型可以是任何类,并且可以有多个具有相同类型的挑战,因此我将挑战存储在以其类型为键的映射中。

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

最终希望实现的目标

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

但我很难弄清楚"Something"里面有什么。如果我放置通配符?符号,那么IntelliJ说handle必须接受需求的一个参数:捕获?当我把它传给A类时,我得到的最好的方法是不指定AbstractChallenge的类型,但我想要一个更好的解决方案。

有什么想法吗?谢谢

你想要的是这样的东西(我在这里接受了评论(:

private Map<Class<?>, List<IChallenge<?>> challenges = new HashMap<>();
A a = ...;
challenges.get(a.getClass())
.removeIf(challenger -> challenger.handle(a));

这是不安全的,因为你不知道t的实际类型,所以你不能做太多(编译器不知道,所以它能做的就是推断它,在这种情况下,类型应该是Object(:

  • 密钥可以是任何类型,例如Integer.class
  • 该值可以是任何类型的IChallenge<T>,如果T不是Integer(或NumberObject,例如:T层次结构中的任何类型(,则如果实现使用它处理的对象并执行某些强制转换,则它可能会失败

添加时:

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

这里有一个例子:

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

如果你想避免这种情况,但仍然能够处理任何挑战,你应该确保持有/构建挑战的类正确地进行:

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

虽然您可以使用您在IChallenge中定义的getType(),但我想向您展示如何强制类型(键(和IChallenge(值(是安全的:通常,除非您将对映射的写访问权限授予其他类,否则这应该是安全的,因为编译器会在插入时验证类型。

因此,删除它们时,由于IChallenge的类型参数,永远不应该有ClassCastException

你也可以尝试使用? super T? extends T,但这是另一个挑战。

--

关于您的评论:

我不完全确定如何使用您指定的addChallenge方法。现在,我有一个班级列表>对于创建的每个挑战,以及应该加载特定挑战的时候,程序都会使用.newInstance((进行实例化。我应该采取不同的做法吗?我只需要一次加载一定数量的挑战,而不是全部——弃用Frank

我并不是告诉你一次加载所有挑战,我只是告诉你使用OOP来确保除了你的挑战持有者(让我们称之为ChallengeHolder(之外,没有人管理映射,并对其进行管理,以避免泛型陷阱:

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

由于除了ChallengeHolder类提供的之外,没有对质询的公共访问,因此使用ObjectClass<T>应该没有问题。

如果您需要按需创建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);
}
}

然后将其添加到ChallengeHolder:

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

或者你可以使用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);
}
}

和:

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

之后,您可以直接使用Supplier来代替ChallengeHolder:中的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);

如果我要实现它,我会使用lambda方法,因为你可以避免反射陷阱(try-catch,可见性问题,特别是考虑到Java 9++引入了模块,…(

上面定义的CCD_ 32可以帮助避免创建多于一个的StringChallenge。在这种情况下,最好让它实现Supplier<T>,而不是IChallenge<T>

这整个离题并没有改变我之前指出的内容:确保只有ChallengeHolder读取/写入映射。

最新更新