繁体   English   中英

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

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

我对 Java 中的 generics 比较陌生,所以如果这是在学校教授的常见内容,我很抱歉(我几乎是自学的)。 假设我有下面的接口和抽象 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;
    }
}

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

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

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

当我尝试将特定的 class 传递给handle方法时,我的问题就出现了。 由于泛型可以是任何 class,并且同一类型可能存在多个挑战,因此我将挑战存储在 map 中,其类型为键。

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

最终希望实现以下目标

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

但是我很难弄清楚“某事”中的内容。 如果我放通配符? 符号,然后 IntelliJ 说handle必须接受要求的参数:捕获? 当我通过它时 class 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层次结构中的任何类型),它可能会失败,如果实现使用 ZA8CFDE63311BD59EB266AC96B 它处理8

添加时:

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

如果您希望避免这种情况,但仍然能够应对任何挑战,则应确保持有/构建挑战的 class 正确完成:

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

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

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

你也可以试试玩? super T ? super T? extends T ? extends T但这是另一个挑战。

--

关于您的评论:

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

我不是要立即加载所有挑战,我只是告诉您使用 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));
  }   

}

由于除了ChallengeHolder class 提供的挑战之外,没有公共访问权限,因此使用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));

之后,您可以直接在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);

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

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

这整个题外话并没有改变我之前指出的:确保只有ChallengeHolder读/写 map。

暂无
暂无

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

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