java eclipse generics javac method-overloading

java - La referencia es ambigua con los genéricos



eclipse generics (3)

Estoy teniendo un caso bastante complicado aquí con genéricos y sobrecarga de métodos. Mira esta clase de ejemplo:

public class Test { public <T> void setValue(Parameter<T> parameter, T value) { } public <T> void setValue(Parameter<T> parameter, Field<T> value) { } public void test() { // This works perfectly. <T> is bound to String // ambiguity between setValue(.., String) and setValue(.., Field) // is impossible as String and Field are incompatible Parameter<String> p1 = getP1(); Field<String> f1 = getF1(); setValue(p1, f1); // This causes issues. <T> is bound to Object // ambiguity between setValue(.., Object) and setValue(.., Field) // is possible as Object and Field are compatible Parameter<Object> p2 = getP2(); Field<Object> f2 = getF2(); setValue(p2, f2); } private Parameter<String> getP1() {...} private Parameter<Object> getP2() {...} private Field<String> getF1() {...} private Field<Object> getF2() {...} }

El ejemplo anterior se compila perfectamente en Eclipse (Java 1.6), pero no con el comando Ant javac (o con el comando javac del JDK), donde recibo este tipo de mensaje de error en la segunda invocación de setValue :

la referencia a setValue es ambigua, tanto el método setValue (org.jooq.Parameter, T) en Test y el método setValue (org.jooq.Parameter, org.jooq.Field) en Test match

De acuerdo con la especificación y, a mi entender, cómo funciona el compilador de Java, siempre se debe elegir el método más específico: http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#20448

En cualquier caso, incluso si <T> está vinculado a Object , lo que hace que ambos métodos setValue candidatos aceptables para la invocación, el que tiene el parámetro Field siempre parece ser más específico. Y funciona en Eclipse, pero no con el compilador de JDK.

ACTUALIZAR :

De esta manera, funcionaría tanto en Eclipse como con el compilador JDK (con advertencias de tipos de materias primas, por supuesto). Entiendo que las reglas especificadas en las especificaciones son bastante especiales, cuando los genéricos están involucrados. Pero encuentro esto bastante confuso:

public <T> void setValue(Parameter<T> parameter, Object value) { } // Here, it''s easy to see that this method is more specific public <T> void setValue(Parameter<T> parameter, Field value) { }

ACTUALIZACIÓN 2 :

Incluso con genéricos, puedo crear esta solución alternativa donde evito que el tipo <T> se setValue a Object en setValue invocation time, al agregar un setValue0 indirecto adicional no ambiguo llamado setValue0 . Esto me hace pensar que la unión de T a Object es realmente lo que está causando todos los problemas aquí:

public <T> void setValue(Parameter<T> parameter, T value) { } public <T> void setValue(Parameter<T> parameter, Field<T> value) { } public <T> void setValue0(Parameter<T> parameter, Field<T> value) { // This call wasn''t ambiguous in Java 7 // It is now ambiguous in Java 8! setValue(parameter, value); } public void test() { Parameter<Object> p2 = p2(); Field<Object> f2 = f2(); setValue0(p2, f2); }

¿Estoy malentendiendo algo aquí? ¿Hay un error de compilación conocido relacionado con esto? ¿O hay una configuración de solución / compilador para ayudarme?

Seguir:

Para aquellos interesados, he presentado un informe de error tanto a Oracle como a Eclipse. Oracle ha aceptado el error, hasta ahora, ¡Eclipse lo ha analizado y lo ha rechazado! Parece que mi intuición es correcta y este es un error en javac


JDK tiene razón. El segundo método no es más específico que el primero. De JLS3 # 15.12.2.5

"La intuición informal es que un método es más específico que otro si cualquier invocación manejada por el primer método podría pasarse a la otra sin un error de tipo de tiempo de compilación".

Este no es claramente el caso aquí. Hice hincapié en cualquier invocación . La propiedad de que un método sea más específico que otro depende puramente de los dos métodos; no cambia por invocación.

Análisis formal de su problema: ¿m2 es más específico que m1?

m1: <R> void setValue(Parameter<R> parameter, R value) m2: <V> void setValue(Parameter<V> parameter, Field<V> value)

Primero, el compilador necesita inferir R de las restricciones iniciales:

Parameter<V> << Parameter<R> Field<V> << R

El resultado es R=V , por reglas de inferencia en 15.12.2.7

Ahora sustituimos R y verificamos las relaciones de subtipo

Parameter<V> <: Parameter<V> Field<V> <: V

La 2da línea no se cumple, por reglas de subtipificación en 4.10.2. Entonces, m2 no es más específico que m1.

V no es un Object en este análisis; el análisis considera todos los valores posibles de V

Sugeriría usar diferentes nombres de método. La sobrecarga nunca es una necesidad.

Esto parece ser un error importante en Eclipse. La especificación indica claramente que las variables de tipo no se sustituyen en este paso. Eclipse aparentemente escribe primero la sustitución de variables, luego verifica la relación de especificidad del método.

Si tal comportamiento es más "sensible" en algunos ejemplos, no está en otros ejemplos. Decir,

m1: <T extends Object> void check(List<T> list, T obj) { print("1"); } m2: <T extends Number> void check(List<T> list, T num) { print("2"); } void test() check( new ArrayList<Integer>(), new Integer(0) );

"Intuitivamente", y formalmente por espec., M2 es más específico que m1, y la prueba imprime "2". Sin embargo, si la sustitución T=Integer se hace primero, ¡los dos métodos se vuelven idénticos!

para la actualización 2

m1: <R> void setValue(Parameter<R> parameter, R value) m2: <V> void setValue(Parameter<V> parameter, Field<V> value) m3: <T> void setValue2(Parameter<T> parameter, Field<T> value) s4: setValue(parameter, value)

Aquí, m1 no es aplicable para la invocación de método s4, por lo que m2 es la única opción.

Según 15.12.2.2, para ver si m1 es aplicable para s4, primero, se lleva a cabo la inferencia de tipo, hasta la conclusión de que R = T; luego comprobamos que Ai :< Si , lo que lleva a Field<T> <: T , que es falso.

Esto es consistente con el análisis anterior: si m1 es aplicable a s4, cualquier invocación manejada por m2 (esencialmente igual que s4) puede ser manejada por m1, lo que significa que m2 sería más específico que m1, que es falso.

en un tipo parametrizado

Considera el siguiente código

class PF<T> { public void setValue(Parameter<T> parameter, T value) { } public void setValue(Parameter<T> parameter, Field<T> value) { } } void test() PF<Object> pf2 = null; Parameter<Object> p2 = getP2(); Field<Object> f2 = getF2(); pf2.setValue(p2,f2);

Esto compila sin problema. Según 4.5.2, los tipos de los métodos en PF<Object> son métodos en PF<T> con sustitución T=Object . Es decir, los métodos de pf2 son

public void setValue(Parameter<Object> parameter, Object value) public void setValue(Parameter<Object> parameter, Field<Object> value)

El segundo método es más específico que el primero.


Mi suposición es que el compilador está haciendo una resolución de sobrecarga de método según JLS, Sección 15.12.2.5 .

Para esta sección, el compilador utiliza una subtipificación fuerte (por lo tanto, no permite ninguna conversión no verificada), por lo tanto, el T value convierte en Object value y el Object value Field<T> value convierte en el Field<T> value Field<Object> value . Se aplicarán las siguientes reglas:

El método m es aplicable al subtipar si y solo si se cumplen las dos condiciones siguientes:

* For 1in, either: o Ai is a subtype (§4.10) of Si (Ai <: Si) or o Ai is convertible to some type *Ci* by unchecked conversion

(§5.1.9), y Ci <: Si. * Si m es un método genérico como se describe arriba, entonces Ul <: Bl [R1 = U1, ..., Rp = Up], 1lp.

(Consulte la viñeta 2). Dado que Field<Object> es un subtipo de Object se encuentra el método más específico. El campo f2 coincide con sus dos métodos (debido a la viñeta 2 anterior) y lo hace ambiguo.

Para String y Field<String> , no hay una relación de subtipo entre los dos.

PD. Esta es mi comprensión de las cosas, no lo cito como kosher.


Editar : Esta respuesta es incorrecta. Eche un vistazo a la respuesta aceptada.

Creo que el problema se reduce a esto: el compilador no ve el tipo de f2 (es decir, Campo) y el tipo inferido de parámetro formal (es decir, Campo -> Campo) como el mismo tipo.

En otras palabras, parece que el tipo de f2 (Campo) se considera un subtipo del tipo de parámetro formal Campo (Campo). Como Field es del mismo tipo, un subtipo de Object, el compilador no puede elegir un método sobre otro.

Editar : Permítanme ampliar mi declaración un poco

Ambos métodos son applicable y parece que la Fase 1: Identificar los métodos de concordancia Aplicable mediante subtipificación se usa para decidir qué método llamar y cuáles reglas de Elegir el método más específico aplicado, pero falló por algún motivo para elegir el segundo método sobre el primero .

La sección de la Fase 1 usa esta notación: X <: S (X es el subtipo de S). Según mi comprensión de <: X <: X es una expresión válida, es decir, <: no es estricta e incluye el tipo en sí mismo (X es el subtipo de X) en este contexto. Esto explica el resultado de la Fase 1: ambos métodos se seleccionan como candidatos, ya que Field<Object> <: Object y Field<Object> <: Field<Object> .

Elegir la sección Método más específico utiliza la misma notación para decir que un método es más específico que otro. La parte interesante del párrafo que comienza con "Un método miembro de ariadia fija llamado m es más específico que otro miembro ...". Tiene, entre otras cosas:

Para todo j de 1 a n, Tj <: Sj.

Esto me hace pensar que, en nuestro caso, el segundo método debe elegirse sobre el primero, porque los siguientes son:

  • Parameter<Object> <: Parameter<Object>
  • Field<Object> <: Object

mientras que al revés no se cumple porque Object <: Field<Object> es falso (Object no es un subtipo de Field).

Nota: En el caso de los ejemplos de Cadena, la Fase 1 simplemente elegirá el único método aplicable: el segundo.

Entonces, para responder a sus preguntas: creo que esto es un error en la implementación del compilador. Eclipse tiene su propio compilador incremental que no tiene este error, parece.