2011-06-29 13 views
15

¿Hay una diferencia semántica entre estas dos declaraciones o solo azúcar sintáctico?Genéricos: T extiende MyClass vs. T extiende MyClass <T>

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

Antecedentes: Recientemente respondieron a un question en los genéricos utilizando el enfoque C<T extends C> y un compañero proporciona una respuesta similar basada en C<T extends C<T>>. Al final, ambas alternativas dieron el mismo resultado (en el contexto de la pregunta). Sigo siendo curioso acerca de la diferencia entre estos dos constructos.

¿Hay una diferencia semántica? Si es así, ¿cuáles son las implicaciones y consecuencias de cada enfoque?

+2

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

+0

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

Respuesta

9

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 SubtractOperationhabrí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.

+0

+1 Gracias por la respuesta detallada. – maasg

+0

No hace referencia a 'Operación' en ningún otro lugar dentro de su código. ¿Desea una' Implements Operation' en alguna parte? – Eric

4

Digamos que usted tiene una clase llamada Animal

class Animal<T extends Animal> 

Si T es el perro, esto significaría que el perro debe ser una subclase de perro < Animal> Animal < Cat>, Animal < burro> nada.

class Animal<T extends Animal<T>> 

En este caso, si T es el perro, que tiene que ser una subclase de Animal Perro <> y nada más, es decir, debe tener una clase

class Dog extends Animal<Dog> 

no se puede tener

class Dog extends Animal<Cat> 

e incluso si usted tiene que, no se puede usar el perro como un parámetro de tipo de animal y debe tener

class Cat extends Animal<Cat> 

En muchos casos hay mucha diferencia entre los dos, especialmente si tiene algunas restricciones aplicadas.

+0

Sí, puede tener 'clase Perro extiende Animal ' en ** ambos ** casos, siempre y cuando 'la clase Cat extienda Animal '. – Marcelo

+0

, siempre y cuando Cat extienda Animal , quiere decir – aps

+0

No. Quiero decir que Perro puede extender Animal , siempre que Cat extienda Animal . La plantilla genérica simplemente indica que T debería extender Animal y Cat ** puede ** extender Animal . – Marcelo

0

Sí, hay una diferencia semántica. Aquí hay una ilustración mínimo:

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

el error es bastante obvia:

desajuste Bound: El tipo D no es un sustituto válido para el parámetro acotado <T extends D<T>> del tipo D<T>

+1

buena observación. – djangofan

Cuestiones relacionadas