Claro, a menudo estos "self types" se utilizan para restringir subtipos para devolver exactamente su propio tipo. Considere algo como lo siguiente:
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
}
}
Genial: tenemos una clase principal con un operador útil que puede ser específico para las subclases. Por ejemplo, si creamos un AddOperation
, ¿cuáles pueden ser sus parámetros genéricos? Debido a la "recursivo" definición genérica, esto sólo puede ser AddOperation que nos da:
public class AddOperation extends AbstractOperation<AddOperation> {
// Methods etc.
}
Y por lo tanto, se garantiza el método copy()
devolver un AddOperation
.Ahora imaginemos que estamos tonto, o malicioso, o creativo, o lo que sea, y tratamos de definir esta clase:
public class SubtractOperation extends AbstractOperation<AddOperation> {
// Methods etc.
// Because of the generic parameters, copy() will return an AddOperation
}
Este será rechazada por el compilador debido a que el tipo genérico no está dentro de sus límites. Esto es bastante importante, significa que en la clase padre, aunque no sabemos cuál es el tipo concreto (e incluso podría ser una clase que no existía en tiempo de compilación), el método copy()
devolverá una instancia de esa misma subclase.
Si simplemente se fue con C<T extends C>
, entonces esta definición extraña de SubtractOperation
habría ser legal, y se pierde la garantía acerca de lo que es T
en ese caso - por lo tanto la operación de resta puede copiarse a sí mismo en una operación de suma.
Esto no se trata tanto de proteger su jerarquía de clases de subclases maliciosas, sino que le da al compilador más garantías sobre los tipos involucrados. Si llama al copy
desde otra clase por alto en un arbitrario Operation
, una de sus formaciones garantiza que el resultado será de la misma clase, mientras que el otro requerirá conversión (y podría no ser un molde correcto, como con el Restar Operación arriba).
Algo como esto, por ejemplo:
// 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;
}
La asignación en la primera línea de duplicate
-T
no sería de tipo seguro con el más débil de los dos saltos se propuso, por lo que el código no lo haría compilar. Incluso si define sensiblemente todas las subclases.
Sé que esto no es lo que está preguntando, pero el primer fragmento incluye un tipo sin procesar, lo que significa que no es código genérico "correcto" - 'C' debe estar parametrizado con algo. Es mi opinión que esto sería un error del compilador si no fuera por la compatibilidad con versiones anteriores. –
Sé que esta tampoco es (realmente) su pregunta, pero no hay una diferencia ** semántica ya que el programa se comportará de la misma manera en ambos casos. –