簡體   English   中英

Java Generics - 需要解釋

[英]Java Generics - explanation needed

我有一個關於Java Generics的問題。 在下面的代碼中,我們有接口B由另一個必須實現接口A的類型參數化。

這段代碼是正確的。 問題是:為什么它不能用於下面的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();
    }
}

我的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();

所以基本上是編譯器沒有正確獲取類型參數的問題,你需要明確地給出類型。

更新:如果你真的遇到一個程序,其中添加useless_t了不同,你處於不安全的地形,並依賴於未指定的編譯器行為。

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

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

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

假設Bird接口具有以下方法:

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

然后你可以像這樣實現它:

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

這是泛型的一個用例 - 允許實現指定具體類型,這樣即使超類也可以使用該類型。 (Bird接口可以有一個setSound(T) ,或者用T做其他東西,而不知道T的具體類型。)

如果調用者只知道一個實例是Bird<? extends Sound> 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> { ... }

我不建議遵循這個例子。 ;-)

我會說:AA通過定義List <AA> l = list()來實現A,你希望它擴展B <X>,而不是。 無論如何,你會看到編寫這樣的代碼會讓你感到困惑。 這太復雜了。

您對Java Generics略有誤解。 要記住的事情,這是一個微妙的事情, 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) ; 我們可以像預期的那樣使用任何有效的Animal實例調用add List上的type參數不會修改或限制列表的內容,它會修改列表本身的類型; 並且“貓列表”不是“動物列表”,也不是“狗列表”。 即使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

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

如果不了解你想做什么,就很難從這里開始並保持相關性。 我並不是說要嚴厲,但看起來你開始在你的代碼中拋出泛型,看看是否有效。 我和Generics一起奮斗多年。 即使經過一個解釋了這個微妙點的博客之后我也不得不重新創建上述的一些變化來強化課程,尋找各種方法,如果我違反了規則,我最終會遇到一個類別轉換異常。 可能你的問題的解決方案是代碼的其他部分沒有很好地定義你想要引入的嚴格類型系統,你看到的泛型問題只是一個症狀。 嘗試減少泛型並更多地依賴於組合和繼承。 我仍然偶爾通過一般的深層射擊自己。 嘗試記住泛型不是為了消除強制轉換,而是為編譯器提供類型信息,以幫助驗證代碼處理類型的正確性,這也很有幫助; 或者換句話說,它將運行時錯誤(類轉換)轉換為源/編譯時錯誤,因此請務必記住編譯時所具有的類型信息之間的區別(即使對於泛型,也是有限的) )以及運行時的類型信息(實例的完整類型信息)。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM