我对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
(或Number
、Object
,例如: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
类提供的之外,没有对质询的公共访问,因此使用Object
或Class<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
读取/写入映射。