2012-06-09 154 views
3

我對Java泛型有個疑問。在下面的代碼中,我們有通過必須實現接口A的另一種類型進行參數化的接口B.Java泛型 - 需要說明

此代碼是正確的。 問題是:爲什麼它不適用於下面的list()方法聲明?

private <X extends A, Y extends B<X>> List<Y> list() 

工作代碼:

public interface A { 
} 
public interface B<T extends A> { 
} 
public class Test { 

    private static class AA implements A {} 
    private static class BB implements B<AA> {} 

    private <R extends A, X extends R, Y extends B<X>> List<Y> list() { 
     return null; 
    } 

    private void test() { 
     List<BB> l = list(); 
    } 
} 

編輯: 我已經修改了代碼。現在,我們擁有可以製造的聲音。問題是爲什麼useless_t是必要的?

public class Test { 
    public interface Sound { 
    } 
    public interface Bird<T extends Sound> { 
    } 

    private static class Quack implements Sound {} 
    private static class Duck implements Bird<Quack> {} 


    private <useless_t extends Sound, sound_t extends useless_t, bird_t extends Bird<sound_t>> List<bird_t> list() { 
      return null; 
    } 

    private void test() { 
      List<Duck> l = list(); 
    } 
} 
+2

你想達到什麼目的? – krock

+0

得到一些書,然後混淆泛型。你完全錯過了他們的觀點。我首先建議Java頭。 –

+0

我第二。您正試圖在該列表類型中指定您的整個繼承層次結構。我不知道你爲什麼想這樣做,但它幾乎肯定不是做事的最佳方式。如果你提供更多關於你想要達到的內容,你可能會得到更好的答案。 – Jochen

回答

3

我的Eclipse IDE沒有按照原樣編譯任何代碼示例。但是,如果有其他類型的提示,它們會進行編譯。在第二個例子中,使用或不使用類型參數useless_t,下面的行不編譯對我來說:

List<Duck> l = list(); 

但下列情況編譯我:

List<Duck> l = this.<Sound, Quack, Duck> list(); 

隨着useless_t分解出來,下面也編譯:

List<Duck> l = this.<Quack, Duck> list(); 

所以它基本上是編譯器沒有得到類型參數的問題,你需要給類型明確。

UPDATE:如果你真的跨過一個程序,添加useless_t了一定的作用來了,你是在不安全的地形,並依靠未指定的編譯器行爲。

您遇到了不同編譯器的行爲不同的問題,即Type Inference。 JLS並不完全清楚編譯器必須推斷類型的位置,以及它必須拒絕推斷的位置,因此這裏有擺動空間。不同版本的Eclipse編譯器和不同版本的javac在它們推斷類型的地方有所不同。對於javac,即使在比較不同的1.5.0_x版本時,情況也是如此,Eclipse編譯器通常可以推斷出多於javac。

您應該只依賴於所有常用編譯器都成功的類型推斷,否則會提供類型提示。有時,這與引入臨時變量一樣容易,但有時(如在您的示例中),您必須使用var.<Types>method()語法。

關於評論:如果我想要方法Duck.getSound()返回嘎嘎,而不是聲音使用泛型?

假設伯德界面有以下方法:

public interface Bird<T extends Sound> { 
    T getSound(); 
} 

然後,你可以實現它,像這樣:

private static class Duck implements Bird<Quack> { 
    public Quack getSound() { return new Quack(); } 
} 

這是一個用例仿製藥 - 允許實現指定具體類型,所以即使超類也可以使用這種類型。 (鳥接口可以有一個setSound(T),或做其他的東西與T,不知道具體類型T的)

如果主叫方只知道一個實例是Bird<? extends Sound>型的,他會打電話給getSound像這樣:

Sound birdSound = bird.getSound(); 

如果來電者知道Quack,他可以執行instanceof測試。但是,如果來電者知道這隻鳥是一個真正的Bird<Quack>,或者甚至就是是一個Duck,那麼他可以寫這個並將其編譯爲期望:

Quack birdSound = bird.getSound(); 

但要注意:泛型化界面中使用的種類太多或超類會帶來過度複雜系統的風險。正如Slanec所寫,反思你的真實設計,看看是否真的需要有這麼多的仿製藥。

有一次,我走的太遠,並結束了一個接口層次和兩個實現層次的基礎上,這樣的接口:

interface Tree<N extends Node<N>, 
       T extends Tree<N, T>> { ... } 

interface SearchableTree<N extends SearchableNode<N>, 
         S extends Searcher<N>, 
         T extends SearchableTree<N, S, T>> 
    extends Tree<N, T> { ... } 

我不建議爲榜樣。 ;-)

+0

這個。 'useless_t'確實沒用。另外,私人>列表 list()'立即編譯。 –

+0

這就是說,在這個例子中,Duck應該知道它來自它的構造函數和內部字段,而不是泛型類型。重新思考你真正的設計,看看是否真的需要有這麼多的泛型。 –

+1

有趣的是,它與我的Eclipse(3.7.2,Java6)編譯,但在javac上失敗。但是,如果從Maven編譯的話可以工作 至於設計評論 - 如果我想方法Duck.getSound()返回嘎嘎,而不是聲音使用泛型? –

1

我會說:AA實現,通過定義列表< AA>升=名單()你希望它擴展乙<X>它沒有。無論如何,通過編寫這樣的代碼,你會發現你很容易感到困惑。這太複雜了。

+0

我的不好 - 這是我的例子中的錯誤。固定 –

0

您對Java泛型有一些誤解。要記住的事情,這是一個微妙的事情,List<Y>不是關於列表的內容,而是列表本身的修改。

讓我們外推一點;說我有interface Animalinterface Dog extends Animalinterface Cat extends Animal。 (我只是創造更多的類和接口,因爲我們走。)現在,如果我宣佈,返回動物List<Animal> createList()的方法,沒有什麼錯用下面的代碼:

List<Animal> litter = createList(); 
Cat tabby = new Tabby(); 
litter.add(tabby); 
Dog poodle = new Poodle(); 
litter.add(poodle); 

這是因爲狗是動物,貓是動物;添加類型List<Animal>的方法簽名是add(Animal);如預期的那樣,我們可以用任何有效的動物實例調用add。但List上的類型參數不會修改或限制列表的內容,它會修改列表本身的類型;和「貓列表」不是「動物列表」,也不是「狗列表」。即使createLitter()方法實際返回僅包含Parrot實例的new ArrayList<Animal>(),上面的代碼也沒問題。然而,你不能做的是「縮小」列表的類型。例如,這是一個編譯錯誤:

List<Bird> birds = createList(); // does not compile 

試想一下,如果它被允許,而且createList返回包含我們的虎斑「動物的名單」;以下將導致類別拋出異常:

Bird leaderOfTheFlock = birds.get(0); 

您也不能'擴大'列表的類型。試想一下,如果這是可能的:

List<Object> things = createList(); // does not compile 

這是不允許任何的原因是,現在的代碼可以添加一個new Integer(0)things - 因爲IntegerObject。顯然,這也不是我們想要的,並且出於同樣的原因 - 「動物列表」不是「對象列表」。 List<Animal>上的類型參數「Animal」修改了列表本身的類型,我們正在討論兩種不同類型的列表。這導致我們得出這一點的第一個結果 - 泛型類型不遵循繼承(is-a)層次結構。

不知道更多你想做什麼,很難從這裏走,並保持相關。我並不是說要苛刻,但它看起來像你開始在代碼中拋出泛型,看看有沒有什麼可行的。我與泛型一起奮鬥了多年。即使在解釋這個微妙點的博客上跑過,我不得不重新創建上面的幾個變體來強化這個教訓,尋找各種方式,如果我違反了規則,我最終會得到一個類演員例外。可能對您的問題的解決方案是,代碼的其他部分對於您正在嘗試引入的嚴格類型系統沒有很好的定義,而您所看到的泛型問題只是其中的一個症狀。嘗試減少泛型並更多地依賴組合和繼承。我仍然偶爾通過去掉通用的深層次而在自己的腳下開槍。嘗試記住泛型的重點不是消除強制類型,而是爲編譯器提供類型信息,以幫助驗證代碼處理類型的正確性。或者換句話說,它會將運行時錯誤(類轉換)轉換爲源代碼/編譯時錯誤,所以務必牢記區分編譯時的類型信息(這是有限的,即使是泛型)以及您在運行時擁有哪些類型信息(這是實例的完整類型信息)。