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 SubtractOperation
był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.
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. –
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. –