2011-06-29 11 views
15

Czy istnieje różnica semantyczna między tymi dwoma deklaracjami czy jest to tylko cukier syntaktyczny?Generics: T przedłuża MyClass vs. T przedłuża MyClass <T>

class C<T extends C> vs class C<T extends C<T>>

Tło: Niedawno odpowiedział question na generycznych stosując metodę C<T extends C> i peer warunkiem podobną odpowiedź opartą na C<T extends C<T>>. Na końcu obie alternatywy dały ten sam rezultat (w kontekście zadawanego pytania). Pozostałem ciekawy różnicy między tymi dwoma konstruktami.

Czy jest różnica semantyczna? Jeśli tak, jakie są konsekwencje i konsekwencje każdego podejścia?

+2

Wiem, że nie o to pytasz, ale pierwszy fragment zawiera surowy typ, co oznacza, że ​​nie jest "prawidłowym" ogólnym kodem - "C" powinno być sparametryzowane z czymś. Uważam, że byłby to trudny błąd kompilatora, gdyby nie kompatybilność wsteczna. –

+0

Wiem, że to nie jest (naprawdę) twoje pytanie, ale nie ma ** semantycznej ** różnicy, ponieważ program będzie zachowywał się w taki sam sposób w obu przypadkach. –

Odpowiedz

9

Oczywiście - często te "typy własne" są używane do ograniczania podtypów, aby zwrócić dokładnie ich własny typ. Rozważmy coś takiego:

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 
    } 
} 

Fajnie - mamy klasę nadrzędną z użytecznym operatorem, który może być specyficzny dla podklas. Na przykład, jeśli tworzymy model AddOperation, jakie mogą być jego parametry ogólne? Ze względu na „” ogólnej definicji rekurencyjnej, to może być tylko AddOperation dając nam:

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

a tym samym gwarantuje metoda copy() do zwracania AddOperation.Teraz pozwala wyobrazić sobie, że jesteśmy głupie, lub złośliwe lub oszczędny, lub cokolwiek, i spróbować zdefiniować tę klasę:

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

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

ta zostanie odrzucona przez kompilator, ponieważ typ rodzajowy nie mieści się w jego granicach. Jest to dość ważne - oznacza to, że w klasie nadrzędnej, nawet jeśli nie wiemy, jaki jest konkretny typ (a może nawet być klasą, która nie istniała w czasie kompilacji), metoda copy() zwróci instancję tej samej podklasy.

Jeśli po prostu poszedł z C<T extends C>, to dziwne określenie SubtractOperationbyłoby być legalne, i tracisz gwarancje o co T jest w tym przypadku - stąd operacja odejmowania można skopiować się do operacji dodawania.

Nie chodzi tylko o ochronę hierarchii klas przed złośliwymi podklasami, ale o to, że daje ona kompilatorowi większe gwarancje dotyczące typów. Jeśli dzwonisz pod numer copy z innej klasy na arbitralnym Operation, jedna z twoich formacji gwarantuje, że wynik będzie tej samej klasy, podczas gdy druga będzie wymagać odlewania (i może być nie być poprawną obsadą, tak jak w przypadku Odejmij Operację powyżej).

Coś takiego na przykład:

// 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; 
} 

Przypisanie na pierwszej linii duplicate do T nie byłoby typu bezpieczne słabszy z dwóch proponowanych granicach ty, więc kod nie będzie skompilować. Nawet jeśli określasz wszystkie podklasy w sposób sensowny.

+0

+1 Dziękujemy za szczegółową odpowiedź. – maasg

+0

Nie odwołujesz się do 'Operacji' gdziekolwiek indziej w twoim kodzie - czy chciałbyś gdzieś tam" implementować Operację "? – Eric

4

Powiedzmy, że masz klasę o nazwie Animal

class Animal<T extends Animal> 

Jeśli T jest pies, oznaczałoby to, że pies powinien być podklasą Zwierząt < Dog> Animal < Cat> Animal < Donkey> cokolwiek.

class Animal<T extends Animal<T>> 

W tym przypadku, jeśli T jest pies, to musi być podklasą Zwierząt < Dog> i nic więcej, to znaczy trzeba mieć klasę

class Dog extends Animal<Dog> 

nie można mieć

class Dog extends Animal<Cat> 

a nawet jeśli masz, że nie można używać Pies jako parametr typu dla zwierząt i trzeba mieć

class Cat extends Animal<Cat> 

W wielu przypadkach występuje duża różnica między tymi dwoma elementami, szczególnie w przypadku zastosowania pewnych ograniczeń.

+0

Tak, możesz mieć 'klasę Dog extends Animal ' w ** obu ** przypadkach, o ile 'klasa Cat rozszerza Animal '. – Marcelo

+0

tak długo jak Cat extends Animal masz na myśli – aps

+0

Nie. Mam na myśli to, że Dog może przedłużyć Animal tak długo jak Cat extends Animal . Ogólny szablon wskazuje tylko, że T powinno rozciągnąć się na Zwierzętę , a Cat ** może ** rozszerzyć Zwierzętę . – Marcelo

0

Tak, istnieje różnica semantyczna. Oto minimalne ilustracji:

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. 
    } 
} 

Błąd jest dość oczywista:

Związany Niedopasowanie: typ D nie jest prawidłową substytut dla ograniczonego parametru <T extends D<T>> typu D<T>

+1

ładna obserwacja. – djangofan

Powiązane problemy