2011-06-29 83 views
15

這兩個聲明之間是否存在語義差異,還是僅僅是語法糖?泛型:T擴展MyClass與T擴展MyClass <T>

class C<T extends C> VS class C<T extends C<T>>

背景:我最近回答了有關使用C<T extends C>方法泛型question和同行提供基於C<T extends C<T>>了類似的回答。最後,兩種選擇都提供了相同的結果(在問題提出的背景下)。我仍然對這兩個構念之間的差異感到好奇。

是否存在語義差異?如果是這樣,每種方法的含義和後果是什麼?

+2

我知道這不是你問的問題,但第一個代碼段包含一個原始類型,這意味着它不是「正確」的通用代碼 - 應該用某些參數化「C」。這是我的觀點,如果不是爲了向後兼容,這將是一個硬編譯器錯誤。 –

+0

我知道這不是(真的)你的問題,但是沒有**語義**區別,因爲程序在這兩種情況下的行爲方式都是相同的。 –

回答

9

的有界參數<T extends D<T>>一個有效的替代品當然 - 這些「自我類型」通常用於限制亞型返回完全屬於自己的類型。考慮如下:

public interface Operation { 
    // This bit isn't very relevant 
    int operate(int a, int b); 
} 

public abstract class AbstractOperation<T extends AbstractOperation<T>> { 
    // Lets assume we might need to copy operations for some reason 
    public T copy() { 
     // Some clever logic that you don't want to copy and paste everywhere 
    } 
} 

酷 - 我們有一個父類,有一個有用的操作符,可以是特定於子類。例如,如果我們創建AddOperation,它的通用參數是什麼?因爲「遞歸」通用的定義,這隻能是AddOperation給我們:

public class AddOperation extends AbstractOperation<AddOperation> { 
    // Methods etc. 
} 

,因此copy()方法是保證返回AddOperation。現在,讓我們來想象我們是愚蠢的,或惡意的,或創意,或什麼的,並試圖定義這個類:

public class SubtractOperation extends AbstractOperation<AddOperation> { 
    // Methods etc. 

    // Because of the generic parameters, copy() will return an AddOperation 
} 

這將由編譯,因爲泛型類型不在其範圍內被拒絕。這非常重要 - 這意味着在父類中,即使我們不知道具體類型是什麼(它甚至可能是編譯時不存在的類),copy()方法將返回一個實例同一個子類。

如果簡單地用C<T extends C>去,那麼SubtractOperation這種怪異的定義將是合法的,而你失去了什麼T在這種情況下,擔保 - 因此減法操作可以自身複製到添加操作。

這不是關於如何保護您的類層次結構免受惡意子類攻擊,更重要的是它能夠讓編譯器對涉及的類型提供更強的保證。如果你從其他職業altogther調用copy任意Operation,你的陣型之一保證結果將是同一類,而另一個將需要施放(和可能不是一個正確的演員,如同上面的SubtractOperation)。

事情是這樣的,例如:

// This prelude is just to show that you don't even need to know the specific 
// subclass for the type-safety argument to be relevant 
Set<? extends AbstractOperation> operations = ...; 
for (AbstractOperation<?> op : operations) { 
    duplicate(op); 
} 

private <T extends AbstractOperation<T>> Collection<T> duplicate(T operation) { 
    T opCopy = operation.copy(); 
    Collection<T> coll = new HashSet<T>(); 
    coll.add(operation); 
    coll.add(opCopy); 

    // Yeah OK, it's ignored after this, but the point was about type-safety! :) 
    return coll; 
} 

上的duplicateT不會是類型安全的與你提出的兩個邊界較弱第一線的分配,因此代碼不會編譯。 即使您明智地定義了所有的子類。

+0

+1感謝您的詳細解答。 – maasg

+0

你不會在你的代碼中的其他地方引用'Operation' - 你想在那裏的'implements Operation'嗎? – Eric

4

比方說,你有一個叫做動物

class Animal<T extends Animal> 

如果T是狗類,那豈不是狗應該是動物<狗的一個子類>,動物<貓>,動物<驢>東西。

class Animal<T extends Animal<T>> 

在如果T是狗這種情況下,它必須是動物<狗>,別無其他的子類,即你必須有一個類

class Dog extends Animal<Dog> 

你不能有

class Dog extends Animal<Cat> 

即使你有,你不能用犬作爲動物類型參數,你必須有

class Cat extends Animal<Cat> 

在許多情況下,兩者之間存在很多差異,特別是如果您應用了某些約束。

+0

是的,只要'Cat Cat擴展動物',你就可以在**這兩種情況下有'class Dog擴展動物'。 – Marcelo

+0

只要貓擴展動物你的意思是 – aps

+0

不,我的意思是,只要貓擴展動物,狗可以擴展動物。通用模板只是表明T應該擴展動物和Cat **可以**擴展動物。 – Marcelo

0

是的,有一個語義上的差異。這裏有一個最小的說明:

class C<T extends C> { 
} 

class D<T extends D<T>> { 
} 

class Test { 
    public static void main(String[] args) { 
     new C<C>(); // compiles 
     new D<D>(); // doesn't compile. 
    } 
} 

錯誤是相當明顯的:

約束不匹配:類型D不是類型D<T>

+1

不錯的觀察。 – djangofan

相關問題