¿Por qué el autoboxing hace que algunas llamadas sean ambiguas en Java?
compiler-construction overloading (6)
Entonces, ¿por qué falló la resolución de sobrecarga? ¿Por qué el compilador no seleccionó automáticamente el primer argumento y aceptó el segundo argumento normalmente? ¿Por qué tengo que solicitar auto-boxing de forma explícita?
No aceptó el segundo argumento normalmente. Recuerde que "boolean" también se puede incluir en un Objeto. También podría haber lanzado explícitamente el argumento booleano a Object y hubiera funcionado.
Hoy noté que el auto-boxing 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 autobloqueo explícito:
static void m(int a, boolean b) { f((Object)a, b); }
Que correctamente llama a la primera sobrecarga como se esperaba.
Entonces, ¿por qué falló la resolución de sobrecarga? ¿Por qué el compilador no seleccionó automáticamente el primer argumento y aceptó el segundo argumento normalmente? ¿Por qué tengo que solicitar auto-boxing de forma explícita?
Cuando dices f (a, b ), el compilador no sabe a qué función debe hacer referencia.
Esto es porque a es un int , pero el argumento esperado en f es un objeto. Entonces el compliler decide convertir a a un Objeto. Ahora el problema es que, si a se puede convertir 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 un Objeto manualmente, el compilador busca la coincidencia más cercana y luego se refiere a ella.
¿Por qué el compilador no seleccionó la función que se puede alcanzar "haciendo la menor cantidad posible de conversiones de boxeo / unboxing"?
Ver 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ó darle una advertencia en su lugar.
Como no hay forma de saber cuál de las funciones el programador realmente pretendía llamar, el compilador da un error.
El compilador hizo auto-box el primer argumento. Una vez hecho esto, es el segundo argumento ambiguo, ya que podría verse como booleano u Objeto.
Esta página explica las reglas para el autoboxing y selecciona qué método invocar. El compilador primero intenta seleccionar un método sin usar ningún autoboxing , porque el boxeo y el unboxing tienen penalizaciones de rendimiento. Si no se puede seleccionar ningún método sin recurrir al boxeo, como en este caso, entonces el boxeo está sobre la mesa para todos los argumentos a ese método.
Ver http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#20448
El yeso ayuda porque no se necesita boxeo para encontrar el método para llamar. Sin el elenco, el segundo intento es permitir el boxeo y luego también se puede encasillar el booleano.
Es mejor tener especificaciones claras y comprensibles para decir qué sucederá que hacer que la gente adivine.
Cuando lanza el primer argumento a Object usted mismo, el compilador correlacionará el método sin utilizar el autoboxing (JLS3 15.12.2):
La primera fase (§15.12.2.2) realiza la resolución de sobrecarga sin permitir la conversión de boxeo o unboxing, o el uso de la invocación de método de aridad variable. Si no se encuentra ningún método aplicable durante esta fase, el procesamiento continúa hasta la segunda fase.
Si no lo haces explícitamente, irá a la segunda fase de intentar encontrar un método de coincidencia, lo que permite el autoboxing, y luego es de hecho ambiguo, porque tu segundo argumento puede coincidir con Boolean u Object.
La segunda fase (§15.12.2.3) realiza la resolución de sobrecarga al tiempo que permite el boxeo y el desempaquetado, pero todavía impide el uso de la invocación del método de aridad variable.
¿Por qué, en la segunda fase, el compilador no elige el segundo método porque no es necesario el autobobinado del argumento booleano? Debido a que después de encontrar los dos métodos de coincidencia, solo la conversión de subtipo se usa para determinar el método más específico de los dos, independientemente de cualquier boxeo o unboxing que haya tenido lugar para coincidir con ellos en primer lugar (§15.12.2.5).
Además: el compilador no siempre puede elegir el método más específico según la cantidad de auto (un) boxeo que se necesita. 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 (tiempo de compilación, paso 2) se soluciona y se describe en el JLS. Una vez en la fase 2, no hay autoboxing selectivo o unboxing. El compilador localizará todos los métodos que son accesibles (ambos métodos en estos casos) y aplicable (nuevamente los dos métodos), y solo entonces escogerá el más específico sin mirar el boxeo / unboxing, que aquí es ambiguo.
El compilador de Java resuelve los métodos y constructores sobrecargados en fases. En la primera fase [§15.12.2.2], identifica los métodos aplicables mediante el subtipado [§4.10]. En este ejemplo, ninguno de los métodos es aplicable, porque int no es un subtipo de Object.
En la segunda fase [§15.12.2.3], el compilador identifica los métodos aplicables por conversión de invocación de método [§5.3], que es una combinación de autoboxing y subtipificación. El argumento int se puede convertir en un Integer, que es un subtipo de Object, para ambas sobrecargas. El argumento booleano no necesita conversión para la primera sobrecarga y se puede convertir a Boolean, un subtipo de Object, para el segundo. Por lo tanto, ambos métodos son aplicables en la segunda fase.
Como es aplicable más de un método, el compilador debe determinar cuál es el más específico [§15.12.2.5]. Compara los tipos de parámetros, no los tipos de argumentos, y no los autocaptura. Object y boolean 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 que escriba Boolean, que es un subtipo de Object. La primera sobrecarga siempre será más específica (cuando corresponda) que la segunda.