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.