2011-10-28 10 views
6

En mi proyecto actual, tengo clases que se modelan de la siguiente manera. En algún momento, se llama a un método como getReturnTypeForGetId() en las clases A y B. Llamar al método con A devuelve Integer como se esperaba, pero B devuelve Serializable.Herencia y llamadas genéricas GetMethod(). GetReturnType()

¿Qué me falta aquí? ¿Estoy siendo mordido por alguna cosa de borrado atroz, o simplemente me estoy perdiendo de algún tipo de engatusamiento de contexto genérico?

EDIT: Adición de una sobre-montado getId() método para B corrige el problema, pero todavía me gustaría entender lo que estoy corriendo en.

import java.io.Serializable; 

public class WeirdTester { 
    static interface Identifiable<T extends Serializable> { 
     T getId(); 
     void setId(final T id); 
    } 

    static abstract class BaseEntity<T extends Serializable> implements Identifiable<T> { 
     private T id; 
     public T getId() { return id; } 
     public void setId(final T id) { this.id = id; } 
    } 

    static class A implements Identifiable<Integer> { 
     private Integer id; 
     public Integer getId() { return id; } 
     public void setId(final Integer id) { this.id = id; } 
    } 

    static class B extends BaseEntity<Integer> {} 

    @SuppressWarnings("unchecked") 
    private static <T extends Serializable, Q extends Identifiable<T>> Class<T> getReturnTypeForGetId(
      final Class<Q> clazz) throws Exception { 
     return (Class<T>) clazz.getMethod("getId", (Class[])null).getReturnType(); 
    } 

    public static void main(final String[] args) throws Exception { 
     System.out.println(getReturnTypeForGetId(A.class)); 
     // CONSOLE: "class java.lang.Integer" 
     System.out.println(getReturnTypeForGetId(B.class)); 
     // CONSOLE: "interface java.io.Serializable" 
    } 
} 
+0

¿No sería más fácil lograr lo que está intentando con tokens de tipo súper? http://gafter.blogspot.com/2006/12/super-type-tokens.html – millimoose

Respuesta

2

Hay varios métodos getId en la clase compilada A. Obtiene un método de puente para el tipo de retorno covariante (una "ficción" del idioma no reflejado en la máquina virtual). La especificación para Class.getMethod dice que devolverá el método con el tipo de devolución más específico (suponiendo que exista). Hace esto para A, pero para B el método no se anula, por lo que javac evita sintetizar un método puente innecesario.

De hecho, para este ejemplo toda la información aún está allí en los archivos de clase. (Antes he dicho que no era erased. Eso no es cierto, pero el borrado no quiere decir que no está allí!) La información genérica Sin embargo, es un poco difícil de extraer (que va a ser en Identifiable.class.getGenericReturnType(), Identifiable.class.getTypeParameters(), BaseEntity.class.getGenericInterfaces, BaseEntity.class.getTypeParameters() y B.getGenericSuperclass (¡creo!).

Utilice javap para ver exactamente lo que tiene en los archivos de clase.

+0

No estoy seguro de entender tu respuesta. Para mí, si agregas setId2 (identificación Serializable final) en BaseEntity, el campo id puede contener cualquier Serializable no solo Integer, por lo tanto, si getId() no se reemplaza en B (y se fuerza para que solo devuelva Integer) cualquier Serializable puede salir de eso Comprobar que ningún método puede establecer el campo id en otra cosa que no sea Integer no es trabajo del compilador/JVM. – MarianP

+0

Para implementar 'setId2' para que realmente funcione,' this.id = (T) id; ', daría una advertencia (que debe tenerse en cuenta !!). 'getId' en una instancia de' B' arrojaría una 'ClassCastException' de la persona que llama (creo). –

+0

(creo ... incorrectamente.) Un error que se corrigió desde el principio fue que javac lanzará a la sobrecarga correcta. Entonces, si 'T' era' char [] 'eso sería CCE. –

2

En la clase A anula getId para devolver enteros.

En la clase B no anula getId, por lo que el método getId en B es el de BaseEntity. Debido al borrado, ese devuelve Serializable.

+0

mal. Esto no tiene nada que ver con el borrado. BaseEntity.id puede contener cualquier Serializable. (La inferencia de que ningún método lo establece en ningún otro Serializable es muy superior a lo que hace el compilador). Si B no reemplaza a getId(), donde se aplicaría que getId() solo devuelva Integer, cualquier serializable puede salir de getId() – MarianP

+0

@MarianP: creo que es conveniente volver a leer sobre qué tipo de borrado significa, ya que * sí * ocurre en el caso mencionado. – JimmyB

+0

@MarianP Usted dice "incorrecto", pero el resto de su comentario es especulación. ¿Has intentado descompilar el código para ver qué hace javac? Yo tengo. La aplicación de genéricos se realiza a través de una combinación de pruebas estáticas (que solo son válidas cuando no hay conversiones no verificadas) y lanzamientos en tiempo de ejecución. –

1

La respuesta es, en efecto tipo de borrado. Recuerde que los genéricos son solo un truco, insinúa el código Java no compilado. El compilador elimina todo lo que tiene que ver con ellos para producir bytecode. Entonces, cuando usa la reflexión en el método getId, solo obtiene el tipo sin procesar.

http://download.oracle.com/javase/tutorial/java/generics/erasure.html

Pero si le preguntas a la clase de un objeto real devuelto por este método (B.getId), sin necesidad de utilizar la reflexión, debido a la forma en que está construido, obtendrá un número entero.

+0

mal. el BaseEntity.id puede contener cualquier Serializable. (La inferencia de que ningún método lo establece en ningún otro Serializable es muy superior a lo que hace el compilador). Si B no anula getId(), donde se aplicaría que getId() solo devuelve Integer, cualquier serializable puede salir de getId() – MarianP

-1

Java permite la llamada "estrechamiento" de tipos de valores de retorno. Es por eso que el ejemplo funciona en absoluto:

Serializable getId()

Se pueden anular con cualquier tipo de retorno serializable, como

Integer getId(), como Integer implementos Serializable, por lo que se permite el estrechamiento en este caso.

Debido B hace no override getId() su getId() es el mismo que el uno heredado de BaseEntity. La declaración

class B extends BaseEntity<Integer> 

es "tipo-borrados" en tiempo de compilación a

class B extends BaseEntity 

y, voilà, recibimos el resultado observado.

+0

, ¿quiere decir retornos covariantes? solo dice que puede usar un subtipo de tipo de retorno en el método reemplazado. No tiene relación con esta pregunta. – MarianP

+0

Lo sentimos, pero no entiendo el punto: para la clase genérica (y por lo tanto para * B *) anterior, * T getId() * se compilará a * Serializable getId() *. Es por eso que, por reflexión, se devuelve el tipo de devolución * Serializable *. En la clase * A * * Entero getId() * anula el heredado * Serializable getId() * porque * Entero * es serializable. Por lo tanto, cuando se compile, el método de la clase * A * devolverá un * Entero *, que es exactamente lo que el reflejo le indica en el tiempo de ejecución, como lo observó el OP. – JimmyB

+0

esto es cierto. Realmente no razonaste todo esto en tu respuesta. Edite algo de carbonilla en su respuesta, por favor, para poder cancelar mi voto negativo. – MarianP

1

Identificación en BaseEntity es privada y ' Serializable o extender Serializable'.

La clase B (que extiende BaseEntity) no sabe nada sobre este campo. Si se define su propio ID y no anula getId()/setId (...) esos dos métodos continuarían utilizando la BaseEntity.id

Si añade este método en el BaseEntity:

public void setId2(final Serializable id) { 
     this.id = (T) id; 
} 

le permite establecer BaseEntity.id a cualquier Serializable.

En la siguiente prueba, puede configurar el campo de id como p. Ej. un valor Float y todo compila y no cambia getId() cómodamente devuelve el valor Float.

B b = new B(); 
b.setId2(2.1F); 
System.out.println(b.getId()); //prints out 2.1 

Por lo tanto, si haces lo que haces y preguntar '¿Cuál es el tipo de retorno del método B.getId()', a continuación, a menos que anule método en la clase B getId() (lo que obligaría a utilizar el tipo de función Entero y devuelve Entero seguro. Tenga en cuenta que BaseEntity.id ¡ni siquiera sería visible para B!) la respuesta de la reflexión no es Integer sino un Serializable genérico. Porque cualquier Serializable puede salir realmente del método getId().