2009-01-17 11 views
7

Me encontré con una situación en la que Java no estaba invocando el método que esperaba: Aquí está el caso de prueba mínima: (Lo siento, parece artificial: el escenario del "mundo real" es sustancialmente más complejo y hace mucho más sentido de un "¿por qué demonios harías ese?" punto de vista.)¿Cómo funciona el envío de métodos Java con Generics y clases abstractas?

Estoy especialmente interesado en por qué sucede esto, no me importa volver a diseñar las sugerencias. Tengo la sensación de que está en Java Puzzlers, pero no tengo mi copia a mano.

Véase la pregunta específica en elogia a prueba <T> .getValue() a continuación:

public class Ol2 { 

    public static void main(String[] args) { 
     Test<Integer> t = new Test<Integer>() { 
      protected Integer value() { return 5; } 
     }; 

     System.out.println(t.getValue()); 
    } 
} 


abstract class Test<T> { 
    protected abstract T value(); 

    public String getValue() { 
     // Why does this always invoke makeString(Object)? 
     // The type of value() is available at compile-time. 
     return Util.makeString(value()); 
    } 
} 

class Util { 
    public static String makeString(Integer i){ 
     return "int: "+i; 
    } 
    public static String makeString(Object o){ 
     return "obj: "+o; 
    } 
} 

La salida de este código es:

obj: 5 

Respuesta

6

No, el tipo de valor no se disponible en tiempo de compilación. Tenga en cuenta que javac solo compilará una copia del código que se utilizará para todas las posibles T. Dado que, el único tipo posible para el compilador para usar en su método getValue() es Object.

C++ es diferente, porque eventualmente creará múltiples versiones compiladas del código según sea necesario.

+0

Ah ... pensé que los genéricos de Java se compilaban más como plantillas de C++. ¡Gracias! – rcreswick

2

Porque la decisión sobre qué makeString() usar se realiza en tiempo de compilación y, en función del hecho de que T podría ser cualquier cosa, debe ser la versión Object. Piénsalo. Si lo hizo Test<String>, debería llamar a la versión Object. Como tal, todas las instancias de Test<T> usarán makeString(Object).

Ahora bien, si usted hizo algo como:

public abstract class Test<T extends Integer> { 
    ... 
} 

las cosas podrían ser diferentes.

2

Josh Bloch's Efectivo Java tiene una discusión excelente que aclara la confusión que surge porque el envío funciona de manera diferente para métodos sobrecargados vs reemplazados (en una subclase). La selección entre métodos sobrecargados --- el tema de esta pregunta --- se determina en tiempo de compilación; selección entre anulado métodos se realiza en tiempo de ejecución (y por lo tanto obtiene el conocimiento del tipo específico del objeto.)

El libro es mucho más claro que mi comentario: Ver "Artículo 41: Uso sobrecarga juiciosamente"

Cuestiones relacionadas