valores una sobrecarga sintaxis retornan resueltos que objetos miembros metodos metodo ejercicios ejemplos clases clase atributos java generics casting type-conversion method-overriding

una - Anular un método en Java genéricamente



sintaxis de un metodo en java (8)

Si tengo una clase base como esta que no puedo cambiar:

public abstract class A { public abstract Object get(int i); }

Y trato de extenderlo con una clase B como esta:

public class B extends A{ @Override public String get(int i){ //impl return "SomeString"; } }

todo está bien. Pero mi intento de hacerlo más genérico falla si lo intento:

public class C extends A{ @Override public <T extends Object> T get(int i){ //impl return (T)someObj; } }

No puedo pensar en ninguna razón por la cual esto debería ser rechazado. A mi entender, el tipo genérico T está vinculado a un Object que es el tipo de retorno solicitado de A Si puedo poner String o AnyObject como mi tipo de retorno dentro de B , ¿por qué no puedo poner <T extends Object> T dentro de mi clase C ?

Otro comportamiento extraño, desde mi punto de vista, es que un método adicional como este:

public class D extends A{ @Override public Object get(int i){ //impl } public <T extends Object> T get(int i){ //impl } }

Tampoco está permitido, con la sugerencia de un DuplicateMethod proporcionado. Este, al menos, me confunde, y creo que Java debería tomar una decisión: si es el mismo tipo de devolución, ¿por qué no permitir la anulación? y si no lo es, ¿por qué no podría agregar este método? Decir que es lo mismo, pero no permitir que se anule, es muy extraño, basado en el sentido común.


Bueno, para la primera parte, la respuesta sería que Java no permite que los métodos no genéricos sean anulados por métodos genéricos, incluso si el borrado es el mismo. Significa que no funcionaría incluso si solo tuviera el método de anulación como:

public <T extends Object> Object get(int i)

No sé por qué Java plantea esta limitación (pensé un poco), solo creo que tiene que ver con casos especiales implementados para tipos genéricos de subclasificación.

Su segunda definición se traduciría esencialmente a:

public class D extends A{ @Override public Object get(int i){ //impl } public Object get(int i){ //impl } }

que obviamente es un problema.


De la sección 8.4.8.3 de JLS Anular y ocultar :

Es un error de tiempo de compilación si una declaración de tipo T tiene un método miembro m1 y existe un método m2 declarado en T o un supertipo de T, de manera que se cumplen todas las condiciones siguientes :

  1. m1 y m2 tienen el mismo nombre.

  2. m2 es accesible desde T.

  3. La firma de m1 no es una subscripción (§8.4.2) de la firma de m2.

  4. La firma de m1 o algún método de anulación de m1 (directa o indirectamente) tiene el mismo borrado que la firma de m2 o algún método de anulación de m2 (directa o indirectamente).

1 y 2 espera.

3 se mantiene también porque (cita de la sección 8.4.2 de JLS ):

La noción de subscripción está diseñada para expresar una relación entre dos métodos cuyas firmas no son idénticas, pero en las cuales uno puede anular el otro. Específicamente, permite que un método cuya firma no use tipos genéricos anule cualquier versión generada de ese método.

Y lo está haciendo de otra manera: un método con un tipo genérico que reemplaza a uno sin genérico.

4 se mantiene también porque las firmas borradas son las mismas: public Object get(int i)


Deberías leer sobre erasure .

En tu ejemplo:

public class D extends A{ @Override public Object get(int i){ //impl } public <T extends Object> T get(int i){ //impl } }

El código de byte generado por el compilador para su método genérico será idéntico a su método no genérico; es decir, T se reemplazará con el límite superior, que es Object . Es por eso que está recibiendo la advertencia DuplicateMethod en su IDE.


Declarar el método como <T extends Object> T get(int i) no tiene sentido sin declarar T en otro lugar, en los argumentos del método o en un campo de la clase adjunta. En este último caso, simplemente parametrice toda la clase con el tipo T


El código original de @RafaelT da como resultado este error de compilación ...

C.java:3: error: C is not abstract and does not override abstract method get(int) in A public class C extends A { ^ C.java:5: error: name clash: <T>get(int) in C and get(int) in A have the same erasure, yet neither overrides the other public <T extends Object> T get ( int i ){ ^ where T is a type-variable: T extends Object declared in method <T>get(int)

La solución más simple y directa para que la subclase C de @ RafaelT se compile correctamente, es ...

public abstract class A { public abstract Object get(int i); }

public class C<T> extends A { @Override public T get(int i){ //impl return (T)someObj; } }

Aunque estoy seguro que otras personas tuvieron buenas intenciones con sus respuestas. Sin embargo, algunas de las respuestas parecen haber malinterpretado el JLS .

El cambio anterior a solo la declaración de clase, resulta en una compilación exitosa. Ese solo hecho significa que las firmas originales de @RafaelT están perfectamente bien como suscripciones , contrariamente a lo que otros han sugerido .

No voy a cometer el mismo error que otros que respondieron parece haber cometido, y trataré de fingir que estoy completamente a punto de asimilar la documentación genérica de JLS. Confieso que no me he dado cuenta con un 100% de certeza, la causa principal del fallo de compilación original del OP.

Pero sospecho que tiene algo que ver con una desafortunada combinación de uso del Object por parte de OP como el tipo de retorno por encima de las sutilezas típicas confusas y extravagantes de los genéricos de Java.


Hay un problema conceptual. Supongamos que C#get() anula A#get()

A a = new C(); Object obj = a.get(); // actually calling C#get()

pero C#get() requiere una T - ¿qué debería ser T ? No hay manera de determinar.

Puede protestar que no se requiere T debido a la eliminación. Eso es correcto hoy. Sin embargo, el borrado se consideró una solución "temporal". El sistema de tipos en la mayor parte no asume borrado; en realidad, está cuidadosamente diseñado para que pueda hacerse completamente "confiable", es decir, sin borrado, en una versión futura de Java sin romper el código existente.


Imagina la siguiente invocación.

A a = new C(); a.get(0);

En efecto, está llamando a un método genérico, pero no está pasando ningún tipo de argumentos. Como están las cosas, esto no es un gran problema. Esos argumentos tipo desaparecen durante la generación de código de todos modos. Sin embargo, la reificación nunca se ha retirado de la mesa y los administradores de Java, el lenguaje, han intentado y siguen intentando mantener esa puerta abierta. Si se reificaran los argumentos de tipo, su invocación no proporcionaría ninguno a un método que requiera uno.


JLS # 8.4.2. Método Firma

La firma de un método m1 es una subscripción de la firma de un método m2 si:

  • m2 tiene la misma firma que m1, o

  • la firma de m1 es la misma que el borrado (§4.6) de la firma de m2.

De acuerdo con la regla anterior, como su padre no tiene un borrado y su hijo tiene uno por lo que no es una anulación válida.

JLS # 8.4.8.3. Requisitos para anular y esconder

Ejemplo 8.4.8.3-4. La eliminación afecta a la anulación

Una clase no puede tener dos métodos miembros con el mismo nombre y borrado de tipo:

class C<T> { T id (T x) {...} } class D extends C<String> { Object id(Object x) {...} }

Esto es ilegal ya que D.id (Objeto) es miembro de D, C.id (String) se declara en un supertipo de D, y:

  • Los dos métodos tienen el mismo nombre, id
  • C.id (String) es accesible a D
  • La firma de D.id (Objeto) no es una subscripción de la de C.id (Cadena)
  • Los dos métodos tienen el mismo borrado.

Dos métodos diferentes de una clase no pueden anular los métodos con el mismo borrado:

class C<T> { T id(T x) {...} } interface I<T> { T id(T x); } class D extends C<String> implements I<Integer> { public String id(String x) {...} public Integer id(Integer x) {...} }

Esto también es ilegal, ya que D.id (String) es un miembro de D, D.id (Integer) se declara en D, y:

  • Los dos métodos tienen el mismo nombre, id
  • D.id (entero) es accesible a D
  • Los dos métodos tienen firmas diferentes (y ninguno es una subscripción del otro)
  • D.id (String) anula C.id (String) y D.id (Integer) anula I.id (Integer) pero los dos métodos sobrescritos tienen el mismo borrado

También da ejemplo de un caso en el que está permitido de super a niño

La noción de subscripción está diseñada para expresar una relación entre dos métodos cuyas firmas no son idénticas, pero en las cuales uno puede anular el otro. Específicamente, permite que un método cuya firma no use tipos genéricos anule cualquier versión generada de ese método. Esto es importante para que los diseñadores de bibliotecas puedan generar libremente métodos independientemente de los clientes que definen subclases o subinterfaces de la biblioteca.

Considera el ejemplo:

class CollectionConverter { List toList(Collection c) {...} } class Overrider extends CollectionConverter { List toList(Collection c) {...}

}

Ahora, asuma que este código se escribió antes de la introducción de los genéricos, y ahora el autor de la clase CollectionConverter decide generar el código, por lo tanto:

class CollectionConverter { <T> List<T> toList(Collection<T> c) {...} }

Sin una dispensación especial, Overrider.toList ya no anulará CollectionConverter.toList. En cambio, el código sería ilegal. Esto inhibiría significativamente el uso de genéricos, ya que los escritores de bibliotecas dudarían en migrar el código existente.