2014-04-15 52 views
2

我想構建一個Java事件發射器,它將有一個使用事件名稱映射的回調(實現Consumer接口)列表。Java通用/通配符類型不匹配

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函數。但是如何?

回答

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。因爲確切類型是未知的,這限制了你可以用它做什麼。

+0

但是'發射器'不會是一個異構容器嗎? –

+0

這會綁定一個Emitter實例到單個EventObject子類型嗎?我試圖讓Emitter實例能夠偵聽EventObject的不同子類型,因爲我只在使用正確子類型定義的lambda函數內部使用特定的子類型方法作爲參數。 –

+0

不,那麼'Emitter'不會是一個異構容器,它確實將'Emitter'綁定到一個'EventObject'子類型。我不認爲這是完全類型安全的方式,沒有強制轉換。 – Jesper