Java通用/通配符类型不匹配

时间:2014-04-15 11:54:42

标签: java generics types lambda

我正在尝试构建一个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 个答案:

答案 0 :(得分: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的特定但未知的类型。因为确切的类型是未知的,这限制了你可以用它做什么。