¿Por qué el autoboxing hace que algunas llamadas sean ambiguas en Java?
-
20-08-2019 - |
Pregunta
Me di cuenta hoy de que el boxeo automático a veces puede causar ambigüedad en la resolución de sobrecarga del método. El ejemplo más simple parece ser este:
public class Test {
static void f(Object a, boolean b) {}
static void f(Object a, Object b) {}
static void m(int a, boolean b) { f(a,b); }
}
Cuando se compila, causa el siguiente error:
Test.java:5: reference to f is ambiguous, both method
f(java.lang.Object,boolean) in Test and method
f(java.lang.Object,java.lang.Object) in Test match
static void m(int a, boolean b) { f(a, b); }
^
La solución a este error es trivial: solo use el auto-boxing explícito:
static void m(int a, boolean b) { f((Object)a, b); }
Que llama correctamente a la primera sobrecarga como se esperaba.
Entonces, ¿por qué falló la resolución de sobrecarga? ¿Por qué el compilador no recuperó automáticamente el primer argumento y aceptó el segundo argumento normalmente? ¿Por qué tuve que solicitar el auto-boxeo explícitamente?
Solución
Cuando lanza el primer argumento a Object usted mismo, el compilador coincidirá con el método sin usar autoboxing (JLS3 15.12.2):
La primera fase (& # 167; 15.12.2.2) realiza resolución de sobrecarga sin permitir conversión de boxeo o unboxing, o la uso del método de aridad variable invocación. Si no hay un método aplicable es encontrado durante esta fase entonces el procesamiento continúa hasta el segundo fase.
Si no lo expulsa explícitamente, pasará a la segunda fase de intentar encontrar un método de coincidencia, permitiendo el autoboxing, y luego es realmente ambiguo, porque su segundo argumento puede coincidir con booleano u Objeto.
La segunda fase (& # 167; 15.12.2.3) realiza resolución de sobrecarga mientras permite boxeo y unboxing, pero aun así impide el uso de aridad variable invocación de método.
¿Por qué, en la segunda fase, el compilador no elige el segundo método porque no es necesario el autoboxing del argumento booleano? Debido a que después de haber encontrado los dos métodos de coincidencia, solo se utiliza la conversión de subtipo para determinar el método más específico de los dos, independientemente de cualquier boxeo o unboxing que se haya realizado para unirlos en primer lugar (& # 167; 15.12. 2.5).
Además: el compilador no siempre puede elegir el método más específico en función del número de auto (des) boxeo necesario. Todavía puede dar lugar a casos ambiguos. Por ejemplo, esto sigue siendo ambiguo:
public class Test {
static void f(Object a, boolean b) {}
static void f(int a, Object b) {}
static void m(int a, boolean b) { f(a, b); } // ambiguous
}
Recuerde que el algoritmo para elegir un método de coincidencia (paso 2 del tiempo de compilación) es fijo y se describe en el JLS. Una vez en la fase 2 no hay autoboxing o unboxing selectivo. El compilador localizará todos los métodos que son accesibles (ambos métodos en estos casos) y aplicables (nuevamente los dos métodos), y solo entonces elige el más específico sin mirar boxing / unboxing, que es ambiguo aquí.
Otros consejos
El compilador hizo auto-box el primer argumento. Una vez hecho esto, es el segundo argumento que es ambiguo, ya que podría verse como booleano u Objeto.
Esta página explica las reglas para autoboxing y seleccionar qué método invocar. El compilador primero intenta seleccionar un método sin usar ningún autoboxing , porque el boxeo y el unboxing conllevan penalizaciones de rendimiento. Si no se puede seleccionar ningún método sin recurrir al boxeo, como en este caso, entonces el boxeo está en la tabla para todos argumentos para ese método.
Cuando dices f (a, b ), el compilador no sabe a qué función debe hacer referencia.
Esto se debe a que a es un int , pero el argumento esperado en f es un Objeto. Entonces el compilador decide convertir a en un Objeto. Ahora el problema es que, si a puede convertirse en un objeto, también puede ser b .
Esto significa que la llamada a la función puede hacer referencia a cualquiera de las definiciones. Esto hace que la llamada sea ambigua.
Cuando convierte a en un Objeto manualmente, el compilador solo busca la coincidencia más cercana y luego se refiere a ella.
¿Por qué el compilador no seleccionó el función que se puede alcanzar con " haciendo el menor número posible de conversiones de boxeo / unboxing " ;?
Vea el siguiente caso:
f(boolean a, Object b)
f(Object a , boolean b)
Si llamamos como f (boolean a, boolean b) , ¿qué función debería seleccionar? Es ambiguo ¿verdad? Del mismo modo, esto se volverá más complejo cuando haya muchos argumentos presentes. Entonces el compilador eligió darte una advertencia en su lugar.
Dado que no hay forma de saber a cuál de las funciones realmente pretendía llamar el programador, el compilador da un error.
Entonces, ¿por qué la resolución de sobrecarga ¿fallar? ¿Por qué el compilador no auto-box el primer argumento, y acepte el segundo argumento normalmente? Por qué yo tener que solicitar auto-boxeo explícitamente?
No aceptó el segundo argumento normalmente. Recuerde que & Quot; boolean & Quot; también se puede encuadrar en un Objeto. También podría haber lanzado explícitamente el argumento booleano a Object y hubiera funcionado.
Consulte http: //java.sun. com / docs / books / jls / third_edition / html / expressions.html # 20448
El reparto ayuda porque entonces no se necesita boxeo para encontrar el método para llamar. Sin el reparto, el segundo intento es permitir el boxeo y luego también el booleano se puede boxear.
Es mejor tener especificaciones claras y comprensibles para decir lo que sucederá que hacer que la gente adivine.
El compilador de Java resuelve métodos y constructores sobrecargados en fases. En la primera fase [& # 167; 15.12.2.2], identifica los métodos aplicables subtipando [& # 167; 4.10]. En este ejemplo, ninguno de los métodos es aplicable, porque int no es un subtipo de Object.
En la segunda fase [& # 167; 15.12.2.3], el compilador identifica los métodos aplicables mediante la conversión de invocación de método [& # 167; 5.3], que es una combinación de autoboxing y subtipo. El argumento int se puede convertir a un entero, que es un subtipo de objeto, para ambas sobrecargas. El argumento booleano no necesita conversión para la primera sobrecarga, y se puede convertir a booleano, un subtipo de objeto, para la segunda. Por lo tanto, ambos métodos son aplicables en la segunda fase.
Dado que se aplica más de un método, el compilador debe determinar cuál es el más específico [& # 167; 15.12.2.5]. Compara los tipos de parámetros, no los tipos de argumentos, y no los autobox. Objeto y booleano son tipos no relacionados, por lo que se consideran igualmente específicos. Ninguno de los métodos es más específico que el otro, por lo que la llamada al método es ambigua.
Una forma de resolver la ambigüedad sería cambiar el parámetro booleano para escribir Boolean, que es un subtipo de Object. La primera sobrecarga siempre sería más específica (cuando corresponda) que la segunda.