2009-02-01 11 views
31

Me di cuenta hoy 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 esta:¿Por qué el autoboxing hace que algunas llamadas sean ambiguas en Java?

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, que hace que 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: sólo tiene que utilizar explícita de auto-boxing:

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 seleccionó automáticamente el primer argumento y aceptó el segundo argumento normalmente? ¿Por qué tengo que solicitar auto-boxing de forma explícita?

Respuesta

31

Cuando lanzas el primer argumento a objeto mismo, el compilador coincidir con el método sin utilizar autoboxing (JLS3 15.12.2):

The first phase (§15.12.2.2) performs overload resolution without permitting boxing or unboxing conversion, or the use of variable arity method invocation. If no applicable method is found during this phase then processing continues to the second phase.

Si no desechan de manera explícita, que se destinará a la segunda fase de intentar encontrar un método de coincidencia, que permita el autoboxing, y de hecho es ambigua, ya que su segundo argumento puede coincidir con Boolean u Object.

The second phase (§15.12.2.3) performs overload resolution while allowing boxing and unboxing, but still precludes the use of variable arity method invocation.

Por qué, en la segunda fase, no el compilador elige el segundo método porque ningún autoboxing del argumento booleano es necesario? 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).

También: el compilador no siempre puede elegir el método más específico en función del número de auto (un) boxeo necesario. Todavía puede dar lugar a casos ambiguos. Por ejemplo, esto es todavía ambigua:

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 la elección de un método de emparejamiento (en tiempo de compilación paso 2) se fija y se describe en el JLS. Una vez en la fase 2, no hay autoboxing selectivo o unboxing. El compilador localizará todos los los métodos que son accesibles (ambos métodos en estos casos) y aplicable (nuevamente los dos métodos), y solo entonces elige el más específico sin mirar boxeo/unboxing, que aquí es ambiguo.

+1

Gracias @eljenso. Esto aclara el problema del compilador, pero luego me pregunto por qué la segunda fase se define así. ¿No podría modificarse con "hacer la menor cantidad posible de conversiones de boxeo/unboxing"? –

+0

¡Excelente respuesta! –

+0

Bueno, gracias @eljenso! Estoy de acuerdo en que, en su ejemplo, la llamada es ambigua. Creo que las dos sobrecargas costarán el mismo número de conversiones de boxeo, por lo que la llamada es ambigua. Pero (independientemente del JLS), no veo mi ejemplo ambiguo. ¿Qué piensas? –

2

So why did the overload resolution fail? Why didn't the compiler auto-box the first argument, and accept the second argument normally? Why did I have to request auto-boxing explicitly?

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.

+0

Gracias @Kevin. Sí, podría incluirlo explícitamente, pero no lo hice. Entonces, ¿por qué no eligió la sobrecarga más específica, que en este caso es la primera? –

+0

Porque el compilador no tenía suficiente información para tomar esa decisión por usted. * Ambos * métodos aplicados porque boolean pueden encasillarse en un Objeto – Kevin

+0

No creo que esto responda la pregunta. Cuando se aplican varios métodos, se elige automáticamente el que requiere la menor cantidad de conversión de ampliación. Obtiene un error de ambigüedad solo cuando varios métodos necesitan la misma cantidad de "ampliaciones". Mi conjetura es que el boxeo no cuenta como un aumento. – erickson

3

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

Esto se debe a una es un int, pero el argumento se espera en f es un objeto. Entonces el compliler decide convertir a en un objeto. Ahora el problema es que, si se puede convertir un a 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 busca la coincidencia más cercana y luego se refiere a ella.

Why didn't the compiler select the function that can be reached by "doing the least possible number of boxing/unboxing conversions"?

Véase el siguiente caso:

f(boolean a, Object b) 
f(Object a , boolean b) 

Si llamamos como f (Boolean, boolean b), que funcionan en caso de que seleccione? 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.

Dado que no hay forma de saber cuál de las funciones el programador realmente pretendía llamar, el compilador da un error.

+0

Gracias @Niyaz. Pero incluso si a y b se pueden convertir en objetos, no es necesario. Entonces, el compilador (IMHO) debe convertir a a un objeto, pero para b debe elegir la sobrecarga más específica, que es la primera. ¿Qué pasa con esta lógica? –

+0

Si hay muchos más argumentos en el ejemplo dado, ¿cómo selecciona el compilador la "sobrecarga más específica"? Ese es el problema. – Niyaz

+0

Niyaz. ¿Qué diferencia hace si lanza a Object? Ambas funciones aceptan Objeto. así que diría por instinto que no hace una combinación mejor. –

4

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.

This page explica las reglas para el autoboxing y seleccionando 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 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á en la tabla para todos los argumentos de ese método.

+0

En ese caso, ¿f (Object, boolean) no sería el método más "específico"? –

+0

Gracias @Bill. @Zach me hizo la misma pregunta que tenía en mente. –

+0

Edité mi respuesta para cubrir esa pregunta. –

2

Ver http://java.sun.com/docs/books/jls/third_edition/html/expressions.html#20448

El yeso ayuda, porque entonces no se necesita el boxeo para encontrar el método a 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 lo que sucederá que hacer a la gente adivinar.

+0

Entonces, es como "una vez en la caja automática, podemos ensuciarnos con otros argumentos también" y "si aún no se ha producido el auto-box, intentamos no autoboxear a los demás también". –

+0

Gracias @iny. ¡Me disculpo por no haber votado hoy! –

+0

@litb, sí aparentemente así. Pero me pregunto por qué no están tratando de "realizar la menor cantidad posible de conversiones de boxeo/unboxing". –

1

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.

Dado que se aplica 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.

Cuestiones relacionadas