Java泛型/通配符类型不匹配。

3
我正在尝试构建一个Java事件发射器,其中包含一个与事件名称映射的回调列表(实现Consumer接口)。
import java.util.HashMap;
import java.util.PriorityQueue;
import java.util.function.Consumer;
import java.util.EventObject;

public class Emitter
{
    protected HashMap<String, PriorityQueue<Consumer<? extends EventObject>>> listeners;

    public Emitter()
    {
        this.listeners = new HashMap<String, PriorityQueue<Consumer<? extends EventObject>>>();
    }

    public Emitter on(String eventName, Consumer<? extends EventObject> listener)
    {
        if (!this.listeners.containsKey(eventName)) {
            this.listeners.put(eventName, new PriorityQueue<Consumer<? extends EventObject>>());
        }

        this.listeners.get(eventName).add(listener);

        return this;
    }

    public <E extends EventObject> Emitter emit(E event)
    {
        String eventName = event.getClass().getName();

        for (Consumer<? extends EventObject> listener : this.listeners.get(eventName)) {
            listener.accept(event);
        }

        return this;
    }
}

我收到了这个编译错误:
Emitter.java:31: error: incompatible types: E cannot be converted to CAP#1
            listener.accept(event);
                            ^
  where E is a type-variable:
    E extends EventObject declared in method <E>emit(E)
  where CAP#1 is a fresh type-variable:
    CAP#1 extends EventObject from capture of ? extends EventObject

但是被捕获的类型明显是子类型,所以它应该可以工作(但我知道我漏掉了什么)。

使用方法应该像这样(其中OpenEvent和CloseEvent当然扩展自EventObject):

Emitter em = new Emitter();
em.on("open", (OpenEvent e) -> e.doOpen());
em.on("close", (CloseEvent e) -> e.doClose());
em.emit(new OpenEvent());
em.emit(new CloseEvent());

我认为可能可以通过使用 lambda 函数来指定消费者对象的类型,以实现类型安全。但是如何做到呢?

1个回答

3
那是因为listener的类型为:Consumer<? extends EventObject>(因此,它是某个具体但未知类型的Consumer,该类型继承自EventObject),但您想要它接受类型为E的事件。编译器无法检查通配符指示的未知类型是否等于类型E
为什么你要使用通配符呢?最好摆脱它们,执行以下操作:
public class Emitter<E extends EventObject>
{
    protected HashMap<String, PriorityQueue<Consumer<E>>> listeners;

    public Emitter()
    {
        this.listeners = new HashMap<String, PriorityQueue<Consumer<E>>>();
    }

    public Emitter on(String eventName, Consumer<E> listener)
    {
        if (!this.listeners.containsKey(eventName)) {
            this.listeners.put(eventName, new PriorityQueue<Consumer<E>>());
        }

        this.listeners.get(eventName).add(listener);

        return this;
    }

    public Emitter emit(E event)
    {
        String eventName = event.getClass().getName();

        for (Consumer<E> listener : this.listeners.get(eventName)) {
            listener.accept(event);
        }

        return this;
    }
}

注意:使用 ? extends EventObject 的通配符类型并不意味着可以将任何扩展 EventObject 的对象传递给它;它指定了一个特定的、但未知的扩展 EventObject 类型。因为确切的类型是未知的,所以这限制了你对它的操作。

但是,Emitter 就不会是一个异构容器了,对吧? - Rohit Jain
这会将Emitter实例绑定到单个EventObject子类型吗?我正在尝试使Emitter实例能够侦听不同的EventObject子类型,因为我只在使用正确的子类型作为参数定义lambda函数内的特定子类型方法。 - Giovanni Lovato
不,否则 Emitter 就不会是一个异构容器,它确实会把 Emitter 与单个的 EventObject 子类型绑定。我认为这在完全类型安全的方式下是不可能的,除非进行强制转换。 - Jesper
那请展示给我(们)一种使用异构容器和非类型安全转换的方法。我真的很需要这个。 - Mark Jeronimus
@MarkJeronimus,您可以从Emitter中删除类型参数E,使用Consumer<EventObject>代替Consumer<E>,然后在需要使用EventObject的地方,使用instanceof检查实际类型并将其转换为该实际类型。 - Jesper
那样做是行不通的,因为当MyObject本身是泛型时,我无法执行listeners.add(new MyObject()) - Mark Jeronimus

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接