2010-05-02 21 views
13

¿Existe alguna diferencia práctica entre los siguientes enfoques para imprimir todos los elementos en un rango?Comodines contra métodos genéricos

public static void printA(Iterable<?> range) 
{ 
    for (Object o : range) 
    { 
     System.out.println(o); 
    } 
} 

public static <T> void printB(Iterable<T> range) 
{ 
    for (T x : range) 
    { 
     System.out.println(x); 
    } 
} 

Al parecer, printB implica un adicional controladas echados a Objeto (véase la línea 16), lo que parece bastante estúpido para mí - no lo es todo un objeto de todos modos?

public static void printA(java.lang.Iterable); 
    Code: 
    0: aload_0 
    1: invokeinterface #18, 1; //InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator; 
    6: astore_2 
    7: goto 24 
    10: aload_2 
    11: invokeinterface #24, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 
    16: astore_1 
    17: getstatic #30; //Field java/lang/System.out:Ljava/io/PrintStream; 
    20: aload_1 
    21: invokevirtual #36; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 
    24: aload_2 
    25: invokeinterface #42, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z 
    30: ifne 10 
    33: return 

public static void printB(java.lang.Iterable); 
    Code: 
    0: aload_0 
    1: invokeinterface #18, 1; //InterfaceMethod java/lang/Iterable.iterator:()Ljava/util/Iterator; 
    6: astore_2 
    7: goto 27 
    10: aload_2 
    11: invokeinterface #24, 1; //InterfaceMethod java/util/Iterator.next:()Ljava/lang/Object; 
    16: checkcast #3; //class java/lang/Object 
    19: astore_1 
    20: getstatic #30; //Field java/lang/System.out:Ljava/io/PrintStream; 
    23: aload_1 
    24: invokevirtual #36; //Method java/io/PrintStream.println:(Ljava/lang/Object;)V 
    27: aload_2 
    28: invokeinterface #42, 1; //InterfaceMethod java/util/Iterator.hasNext:()Z 
    33: ifne 10 
    36: return 

Respuesta

7

En su ejemplo, el tipo genérico se utiliza en exactamente un punto de la firma. En este escenario, un tipo T no tiene ninguna ventaja sobre un comodín para la persona que llama. En su ejemplo, el tipo tampoco tiene la ventaja para el implementador del método.

Encuentro que la versión comodín es más fácil de entender para una persona que llama, ya que explícitamente dice "No me importa el tipo en absoluto".

En su ejemplo, el checkcast es realmente superfluo. Sería necesario si T fuera limitado, como en T extends Number. A continuación, se requiere una comprobación para Number, porque la variable local x será del tipo Number, pero el método Iterator.next() aún devuelve Object. Parece que el compilador de Java no se molesta en optimizar el lanzamiento. El JIT probablemente lo haga en tiempo de ejecución.

ACTUALIZACIÓN:

Si se utiliza el tipo genérico en varios lugares, como en la respuesta de Cletus, que no tienen más remedio que utilizar un tipo genérico T, o de lo contrario el compilador ve ninguna conexión entre el tipo de parámetro/tipo de retorno (dos comodines son distintos para el compilador).

Un caso límite es cuando la firma solo tiene el tipo en un punto, pero la implementación necesita que sea un tipo genérico en lugar de un comodín. Piense en un método void swap(List<T> list, int a, int b). Necesita quitar elementos de la lista y volver a colocarlos. IIRC, Java efectiva sugiere utilizar un método público con el comodín y un método interno de ayuda con un tipo que contenga la implementación real. De esta manera, el usuario obtiene una API simple, y el implementador todavía tiene seguridad de tipo.

public void swap(List<?> list, int a, int b){ 
    swapHelper(list, a, b); 
} 
private <T> void swapHelper(List<T> list, int a, int b){ 
    ... 
} 
+0

Estoy de acuerdo: * en este caso * la versión comodín es mejor. – Jorn

2

El segundo es más flexible. Un mejor ejemplo es: está diciendo algo sobre el tipo. Si eso es útil para ti depende de lo que haga la función.

La segunda muestra su utilidad cuando se quiere devolver algo del método:

public static <T> List<T> reverse(List<T> list) { 
    for (int i=0; i<n/2; i++) { 
    T value = list.get(i); 
    list.set(i, list.get(list.size() - i - 1)); 
    list.set(list.size() - i = 1, value); 
    } 
    return list; 
} 

Es posiblemente un mejor ejemplo para copiar la matriz, pero el punto es que realmente no se puede hacer lo anterior con List<?> .

+0

No obtengo esto. Por lo que puedo decir, las dos formas se pueden utilizar en exactamente los mismos contextos y uno no da una iota más o menos tipo de seguridad que la otra. ¿Me estoy perdiendo de algo? –

+1

@Marcelo Si usas?la versión del método de llamada obtendría una lista, y necesita convertir esa lista en una lista . Lo cual es un poco inseguro ya que el compilador no puede verificar que la implementación realmente produzca una lista . El contrato (con?) Establece que se devolverá una lista y eso es cierto, incluso si no es una lista . La versión T de este modo permitiría al compilador verificar el tipo de devolución y el método de llamada sería más seguro. – extraneon

+0

Sí, pero siempre que la T solo aparece en un lugar en la lista de argumentos (como en el ejemplo del OP), se puede convertir en un comodín. Este ejemplo es diferente (la T aparece en dos lugares, incluido en el retorno). – newacct

0

No realmente. El bytecode resultante debe ser virtualmente idéntico.

+0

No lo es, mira mi edición. – fredoverflow

+0

Ah, sí. Entonces, la versión '' realmente comprueba que los contenidos son del tipo correcto y, por lo tanto, pueden fallar si un 'Iterable ' contiene algo que no es 'T'. He suavizado mi respuesta un poco. –

+0

@Marcello: No, no verifica si un Iterable contiene algo que no es una T. Simplemente verifica si contiene algo que no se puede convertir a Object. Pero los genéricos Java están tan estúpidamente implementados que supongo que esto es solo otro residuo ... – Foxfire

Cuestiones relacionadas