繁体   English   中英

Java中generics的正确使用

[英]Correct use of generics in Java

我正在尝试设置我的自定义事件管理系统 - 只是为了学习。

我创建了一个结构:

  • 一个Event - 这是最简单的组件,可以携带任何信息
  • 一个Listener - 将接受一个或多个Event s
  • 一个EventManager负责将任何事件提交给其注册的侦听器

我已经使用 generics 来利用 Java 的强类型,但是我遇到了死胡同,因为编译器“未检查”警告无法通过

课程如下:

事件:

// no functionality, just a placeholder for events
interface Event { }

听众:

interface EventListener<T extends Event> {
    boolean accept(Event event);

    void submit(T event);
}

事件管理器:

public class EventManager {

    private final List<EventListener<? extends Event>> listeners;

    public EventManager(List<EventListener<? extends Event>> listeners) {
        this.listeners = listeners;
    }

    public void sendEvent(Event event) {
        this.listeners.stream()
                .filter(l -> l.accept(event))
                .forEach(l -> l.submit(getEvent(event)));
    }

    private <T extends Event> T getEvent(Event event) {
        return (T) event;  // <-- warning
    }
}

我期待客户致电:

ConcreteEvent event = new ConcreteEvent(); // <-- That implements Event
eventManager.sendEvent(event);

警告在最后一个方法getEvent中,这是有道理的。

我的问题是:

  • 是否有任何替代设计不会引发编译警告并正确使用 java generics? 我想通过EventManager提交任何事件,并且该事件将由一个或多个侦听器处理

Generics 在编译时链接事物(即“在编写代码时”)。 例如,在:

private <T extends Event> T getEvent(Event event) {
  return (T) event;  // <-- warning
}

这并没有完成任何有意义的事情,你只是在打扮一个演员:你已经为这个方法声明了一个T ,但是每当你声明一个 typevar 时,你必须在至少 2 个地方使用它,否则它要么是一个毫无意义的练习,要么(就像这里的情况一样),一个黑客。 你在这里两个地方使用它,但第二个地方是对 T 的强制转换,它绝对什么都不做:这是一个断言:“编译器,闭嘴,我知道我在做什么,相信我,这将是一个 T ”。 无论是在写入时还是在运行时(T)都不会做任何事情。 不执行任何检查,并且此行不可能抛出 ClassCastException。 相反,事件处理程序的代码执行了一个不可见的强制转换,并且该强制转换会失败:您会在一行上获得一个 CCE,但它的强制转换为零,这令人困惑。 这就是警告试图告诉您的内容:编译器实际上无法保证此处的类型安全。

你的问题是这个“在编译时链接事物”的工作在这里是不可能的:你有一个谁知道什么的事件监听器列表(字面意思是: ? extends Event -?=谁知道),因此没有链接 2 个用法在你的代码中。 没有办法添加这样的编译时链接。

好的,那我该怎么办?

如果您同意将事件处理程序可以处理的事件类型限制为仅无类型变量的类型(因此没有NewDataUploadedEvent<X> - 只有本身没有泛型的具体类型),有一个解决方案:通过显式具体化类型传递它。

在我深入研究代码之前,请注意:

您的accept方法接受任何事件,但您的 submit 方法只接受 T。这是糟糕的设计,您应该修复它。 如果您的布尔返回方法仍然接受非 T 事件类型,这意味着什么? 您可以将其传递给submit方法的唯一方法是破坏类型安全,这是没有意义的。 更一般地说,为什么你有一个接受方法? 你只是强迫某人编写一个事件处理程序来复制代码并创建奇怪的问题(“如果一个事件传递给我无法处理的提交方法,我该怎么办?”)。 为什么不只是有一个submit方法,它可以抛出一个异常来表明它不想要它,或者返回一个 boolean 来表明这一点? 我将在接下来的例子中解决这个问题; 我不喜欢在答案中写出糟糕的代码风格; 让我觉得我在教坏习惯。

关于这一点,stream 操作上的forEach终端,特别是如果没有实际的 stream 操作完成(没有映射或过滤或诸如此类)通常是代码味道:您失去控制流、检查异常和局部变量透明度,并且您获得绝对没有任何回报。 只需编写一个for(:)循环。 我知道我知道。 你有一把新锤子,非常 shiny,它让你觉得一切都是钉子。 但是,它不是钉子。 你可以用锤子黄油吐司,但是,黄油刀是更好的工具。 所以就到这里了。 我也会解决的。

  1. 在接口中添加一个Class<T>返回方法:
interface EventListener<T extends Event> {
    Class<T> getType();
    void submit(T event) throws EventNotAcceptedException;
}

现在您可以像这样编写事件循环:

public void sendEvent(Event event) {
  for (EventListener<?> listener : listeners) tryListener(listener, event);
}

private <T extends Event> boolean tryListener(EventListener<T> listener, T event) {
  Class<T> type = listener.getType();
  T obj;
  try {
    obj = type.cast(event);
  } catch (ClassCastException e) {
    return false;
  }

  try {
    listener.submit(obj);
    return true;
  } catch (EventNotAcceptedException e) {
    return false;
  }
}
  1. Go 上反射狂欢

从技术上讲,可以使用反射来获取class Foo extends Bar<X> <X> 但是,您会直接从源文件中获得文字X,而不是实际的 X。 如果你这样写,那就太好了:

class UploadHandler extends EventHandler<UploadEvent> {}

因为这意味着您可以从中获取UploadEvent.class 如果您有以下情况,那就不太好:

class GeneralizedHandler<T> extends EventHandle<T> {
   private final Consumer<T> consumer;
   // boilerplate here
}

因为你只会得到T这不是你需要的,你不能用它来做类型检查和强制转换。 如果有人试图在你身上使用这个特技,你必须排除运行时错误,你无法在编译时捕捉到它。 如果你愿意,好吧,可行,但很复杂。 the jlClass API has the getGenericSuperclass() method, which returns a Type , which is useless (it has no methods), but you'd check if gGS returns a java.lang.reflect.ParameterizedType , and if it does, call its getActualTypeArguments()方法,其第一个参数大概应该是java.lang.Class ,就是这样。 然后,您可以从那里进行投射等,并保证它是“正确的类型”,可以这么说。 但是,如果没有得到编译器警告,就无法编写它。 您可以通过将代码隔离在一个方法中来解决这个问题,就像您在问题中的getEvent方法中所做的那样,并在其上粘贴@SuppressWarnings

您可以在注册事件侦听器时提取正确的类型,而不是将事件侦听器存储为List<EventListener>而是创建一个容器 class: private static class EventContainer<T> { EventListener<T> listener; Class<T> eventType; } private static class EventContainer<T> { EventListener<T> listener; Class<T> eventType; } - 如果有人传递未定义为 extends EventListener private static class EventContainer<T> { EventListener<T> listener; Class<T> eventType; } extends EventListener<SomethingConcrete>的 eventlistener impl,这可以让您尽快抛出异常(尽早抛出总是好的),您现在可以使用该eventType来打电话给cast和朋友-不会有任何警告。

暂无
暂无

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

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