Pregunta ¿Por qué el autoboxing hace que algunas llamadas sean ambiguas en Java?


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?


32
2018-02-01 19:20


origen


Respuestas:


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   resolución de sobrecarga sin permiso   la conversión de boxeo o unboxing, o la   uso del método de aridad variable   invocación. Si no hay un método aplicable   encontrado durante esta fase, entonces   el procesamiento continúa al segundo   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   resolución de sobrecarga al tiempo que permite   boxeo y unboxing, pero aún   Impide el uso de arity 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 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 corrige y se describe en el JLS. Una vez en la fase 2, no hay autoboxing selectivo o unboxing. El compilador localizará todas los métodos que son accesibles (ambos métodos en estos casos) y aplicables (de nuevo los dos métodos), y solo entonces elige el más específico sin mirar el boxeo / unboxing, que aquí es ambiguo.


32
2018-02-01 19:47



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 en absoluto, 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á sobre la mesa para todas argumentos para ese método.


4
2018-02-01 19:38



Cuando tu dices f (a, b), el compilador no sabe a qué función debe hacer referencia.

Esto es porque un es un En t, pero el argumento esperado en F es un Objeto Entonces el compliler decide convertir un a un objeto Ahora el problema es que, si un se puede convertir en un objeto, por lo que puede ser segundo.

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 conviertes un a 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 al "hacer   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 (booleano a, booleano 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.


3
2018-02-01 19:36



Entonces, ¿por qué la resolución de sobrecarga   ¿fallar? ¿Por qué el compilador no se auto-recuperó?   el primer argumento, y aceptar el   segundo argumento normalmente? ¿Por qué   tiene que solicitar auto-boxing   ¿explícitamente?

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.


2
2018-02-01 19:35



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.


2
2018-02-01 19:46



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.


1
2018-01-31 21:42