java overloading wrapper primitive jdk1.6

java - ¿Por qué este método sobrecarga ambiguo?



overloading wrapper (1)

public class Primitive { void m(Number b, Number ... a) {} // widening, autoboxing->widening->varargs void m(byte b, Number ... a) {} // unboxing, autoboxing->widening->varargs public static void main(String[] args) { Byte b = 12; Primitive obj = new Primitive(); obj.m(b, 23); } }

Ya busqué y encontré que la prioridad de ampliación es más alta que el desempaquetado, por lo que en la invocación del método anterior, el primer método debería haber sido llamado porque el segundo parámetro es el mismo para ambos. Pero esto no sucede. ¿Puedes explicar por favor?


No se compila en JDK 1.5, 1.6 y 1.7, pero funciona en JDK 1.8.

Actualización : Parece que el hecho de que funcionó con las primeras versiones de JDK8 fue en realidad un error: funcionó en JDK 1.8.0_05, pero de acuerdo con esta pregunta y la respuesta de medvedev1088, este código ya no se compilará en 1.8.0_25, ¿Cuál es el comportamiento que se ajusta a la JLS?

No creo que este sea un error que se haya solucionado. En cambio, es más bien un efecto de los cambios que están relacionados con los mecanismos de invocación de métodos para las expresiones lambda en Java 8.

La mayoría de la gente probablemente estaría de acuerdo en que la sección sobre "Expresiones de invocación de métodos" es, con mucho, la parte más compleja e incomprensible de la especificación del lenguaje Java. Y probablemente hay todo un equipo de ingenieros preocupados por la verificación y validación de esta sección. Por lo tanto, cualquier declaración o intento de razonamiento debe tomarse con un gran grano de sal. (Incluso cuando se trata de los ingenieros antes mencionados). Pero lo intentaré para, al menos, desarrollar las partes relevantes a las que otros pueden referirse para un análisis más detallado:

Teniendo en cuenta la sección sobre

y considerando que ambos métodos son "Métodos Potencialmente Aplicables" ( JLS7 / JLS8 ), entonces la subsección relevante es la de

Para JLS 7, afirma

El método m es un método de variable variable aplicable si y solo si se cumplen todas las condiciones siguientes:

  • Para 1 = i <n, el tipo de ei, Ai, se puede convertir por conversión de invocación de método a Si.
  • ...

(Las otras condiciones se refieren a formas de invocación que no son relevantes aquí, por ejemplo, invocaciones que realmente usan los varargs o invocaciones que involucran genéricos)

Refiriéndose al ejemplo: un método es aplicable para la expresión de argumento real b de tipo Byte cuando b se puede convertir al parámetro de método formal respectivo mediante la conversión de invocación de método. De acuerdo con la sección correspondiente sobre Conversión de invocación de método en JLS7, se permiten las siguientes conversiones:

  • una conversión de identidad (§5.1.1)
  • una conversión primitiva de ampliación (§5.1.2)
  • una ampliación de la conversión de referencia (§5.1.5)
  • una conversión de boxeo (§5.1.7) opcionalmente seguida de una ampliación de la conversión de referencia
  • una conversión unboxing (§5.1.8) opcionalmente seguida de una conversión primitiva de ampliación.

Obviamente, hay dos métodos que son aplicables de acuerdo con esta especificación:

  • m(Number b, Number ... a) se aplica a través de la conversión de referencia de ampliación
  • m(byte b, Number ... a) se aplica a través de la conversión unboxing

Usted mencionó que "... descubrió que ampliar la prioridad es más alta que desempaquetar" , pero esto no es aplicable aquí: Las condiciones enumeradas anteriormente no implican ninguna "prioridad". Se enumeran como diferentes opciones. Incluso si el primer método fuera void m(Byte b, Number ... a) , la "conversión de identidad" sería aplicable, pero solo contaría como una posible conversión y causaría un método de error debido a la ambigüedad.

Entonces, por lo que entendí, esto explica por qué no funcionó con JDK7. No descubrí en detalle por qué funcionó con JDK8. Pero la definición de aplicabilidad de los métodos de aridad variable cambió ligeramente en los métodos de identificación aplicables mediante la invocación de aridad variable en JLS 8 :

Si m no es un método genérico, entonces m es aplicable por invocación de aridad variable si, para 1 ≤ i ≤ k, o ei es compatible en un contexto de invocación suelto con Ti o ei no es pertinente para la aplicabilidad (§15.12.2.2).

(Todavía no me sumergí más en las definiciones de "contextos de invocación flexible" y en la sección §15.12.2.2, pero esta parece ser la diferencia crucial aquí)

Dejando de lado, una vez más refiriéndose a su declaración de que "... descubrió que la prioridad de ampliación es más alta que unboxing" : Esto es cierto para los métodos que no involucran varargs (y que no requieren la conversión de invocación de método). Si omite las varags en su ejemplo, entonces el proceso de encontrar el método de coincidencia se iniciará en la Fase 1: Identificar los métodos de aridad coincidentes aplicables mediante el subtipo . El método m(Number b) ya sería aplicable para el parámetro Byte b debido a que Byte es un subtipo de Number . No habría ninguna razón para entrar en la Fase 2: identificar los métodos de aridad coincidentes aplicables mediante la conversión de invocación de métodos . En esta fase, se aplicaría la conversión de invocación del método mediante unboxing de Byte a byte , pero esta fase nunca se alcanza.