Promoción de tipo Java en parámetros

20

Me topé con este fragmento:

public class ParamTest {
    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(long a, long b) {
        System.out.println("In long " + (a + b));
    }

    public static void printSum(double a, long b) {
        System.out.println("In doubleLONG " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

Esto dará como resultado un error de compilación:

Error: (15, 9) java: la referencia a printSum es ambigua tanto en el método printSum (int, double) en ParamTest como en el método printSum (long, long) en ParamTest match

¿Cómo es esto ambiguo? ¿No debería promoverse solo el segundo parámetro en este caso ya que el primer parámetro ya es un int? El primer parámetro no necesita ser promovido en este caso, ¿verdad?

La compilación tiene éxito si actualizo el código para agregar otro método:

public static void printSum(int a, long b) {
    System.out.println(String.format("%s, %s ", a, b));
}

Déjame expandirme solo para aclarar. El siguiente código genera ambigüedad:

public class ParamTest {

    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(long a, long b) {
        System.out.println("In long " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

Entonces, este código a continuación también genera ambigüedad:

public class ParamTest {

    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(double a, long b) {
        System.out.println("In doubleLONG " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}

Sin embargo, este no da lugar a la ambigüedad:

public class ParamTest {

    public static void printSum(int a, double b) {
        System.out.println("In intDBL " + (a + b));
    }

    public static void printSum(long a, double b) {
        System.out.println("In longDBL " + (a + b));
    }

    public static void main(String[] args) {
        printSum(1, 2);
    }
}
riruzen
fuente
2
El compilador puede asignar su llamada printSum (1, 2); ya sea printSum (long a, long b) o printSum (int a, double b), por lo tanto, es ambiguo. Tienes que ayudar explícitamente al compilador a elegir dando el tipo exactamente así: printSum (1, 2d)
Ravindra Ranwala
66
Citó mal el mensaje de error y la diferencia es muy importante. El mensaje de error real es: Error:(15, 9) java: reference to printSum is ambiguous both method printSum(int,double) in ParamTest and method printSum(long,long) in ParamTest match- no es el método lo que es ambiguo, es la llamada al método lo que es ambiguo.
Erwin Bolwidt
1
@ErwinBolwidt el mensaje de error fue de eclipse lo siento, hice una cita errónea allí. de todos modos, todavía no lo entiendo porque agregar printSum (int a, long b) elimina el mensaje de error.
riruzen
2
JLS-5.3 => Si el tipo de expresión no se puede convertir al tipo del parámetro mediante una conversión permitida en un contexto de invocación flexible, se produce un error en tiempo de compilación. parece ser aplicable al contexto, pero no es realmente sencillo inferir cómo. +1
Naman
1
Definitivamente necesitamos una pregunta canónica para esto: stackoverflow.com/… . "
Marco13

Respuestas:

17

Creo que esto tiene algo que ver con la regla específica de JLS sobre 15.12.2.5. Elegir el método más específico . Se afirma que:

Si más de un método miembro es accesible y aplicable a la invocación de un método, es necesario elegir uno para proporcionar el descriptor para el envío del método en tiempo de ejecución. El lenguaje de programación Java usa la regla de que se elige el método más específico.

El texto explica cómo Java elige el método más específico :

La intuición informal es que un método es más específico que otro si cualquier invocación manejada por el primer método podría pasar al otro sin un error en tiempo de compilación. En casos como un argumento de expresión lambda tipado explícitamente (§15.27.1) o una invocación de aridad variable (§15.12.2.4), se permite cierta flexibilidad para adaptar una firma a la otra.

En el caso de su ejemplo, todos los métodos son accesibles y aplicables a la invocación de métodos, por lo tanto, Java necesita determinar cuál de ellos es más específico .

Para estos métodos, no se puede determinar que sea más específico:

public static void printSum(int a, double b) {
    System.out.println("In intDBL " + (a + b));
} // int, double cannot be passed to long, long or double, long without error

public static void printSum(long a, long b) {
    System.out.println("In long " + (a + b));
} // long , long cannot be passed to int, double or double, long without error

public static void printSum(double a, long b) {
    System.out.println("In doubleLONG " + (a + b));
} // double, long cannot be passed to int, double or long, long without error

El cuarto método borra la ambigüedad precisamente porque cumple la condición necesaria para ser más específico .

public static void printSum(int a, long b) {
    System.out.println(String.format("%s, %s ", a, b));
}

Es decir, (int, long) puede pasarse a (int, double), (long, long) o (double, long) sin errores de compilación.

alboroto
fuente
2
Entonces, para resumir, si la llamada puede acceder a todos los métodos, entonces Java identifica el método, que se puede llamar a otros métodos sin error de tiempo de compilación, si se encuentra, ¿lo usa como el más específico y lo llama error de ambigüedad? Creo que esta es una respuesta válida @riruzen.
Sandeep Kokate
¡Gracias! Intenté esto con (double, int) vs (double, long) y realmente despejó la ambigüedad, luego (double, int) vs (long, double) fue ambiguo como se esperaba.
riruzen
7

De hecho, es una pregunta muy interesante. Veamos paso a paso la especificación del lenguaje Java.

  1. Cuando el compilador intenta identificar métodos potencialmente aplicables, lo primero que hace es buscar métodos aplicables por invocación estricta .

  2. En su caso, no existen tales métodos, por lo que el siguiente paso es encontrar los métodos aplicables por Invocación flexible

  3. En este punto, todos los métodos coinciden, por lo que el método más específico ( §15.12.2.5 ) se elige entre los métodos que son aplicables por invocación flexible.

Este es un momento clave, así que veamos esto de cerca.

Un método aplicable m1 es más específico que otro método aplicable m2, para una invocación con expresiones de argumento e1, ..., ek, si alguno de los siguientes es verdadero:

(Estamos interesados ​​solo en el siguiente caso):

  • m2 no es genérico, y m1 y m2 son aplicables por invocación estricta o flexible, y donde m1 tiene tipos de parámetros formales S1, ..., Sn y m2 tiene tipos de parámetros formales T1, ..., Tn, el tipo Si es más específico que Ti para el argumento ei para todo i (1 ≤ i ≤ n, n = k).

En pocas palabras, un método es más específico si todos sus tipos de parámetros son más específicos . Y

Un tipo S es más específico que un tipo T para cualquier expresión si S <: T ( §4.10 ).

Expresión S <: Tsignifica que Ses un subtipo de T. Para los primitivos tenemos la siguiente relación:

double > float > long > int

Así que echemos un vistazo a sus métodos y veamos cuál es más específico que otros.

public static void printSum(int a, double b) {  // method 1
    System.out.println("In intDBL " + (a + b));
}

public static void printSum(double a, long b) { // method 2
    System.out.println("In doubleLONG " + (a + b));
}

En este ejemplo, el primer parámetro del método 1 es obviamente más específico que el primer parámetro del método 2 (si los llama con valores enteros:) printSum(1, 2). Pero el segundo parámetro es más específico para el método 2 , porque long < double. Por lo tanto, ninguno de estos métodos es más específico que otro. Es por eso que tienes una ambigüedad aquí.

En el siguiente ejemplo:

public static void printSum(int a, double b) { // method 1
    System.out.println("In intDBL " + (a + b));
}

public static void printSum(long a, double b) { // method 2
    System.out.println("In longDBL " + (a + b));
}

el primer tipo de parámetro del método 1 es más específico que el del método 2, porque int < longy el segundo tipo de parámetro es el mismo para ambos, por eso se elige el método 1.

Kirill Simonov
fuente
No rechacé su respuesta, pero esta respuesta no explica por qué (int, double) es ambiguo con (long, long) ya que claramente, (int, double) debería ser más específico con respecto al primer parámetro.
riruzen
3
@riruzen explica: doubleno es más específico que long. Y para que el método sea elegido, todos los parámetros de tipo deben ser más específicos: el tipo Si es más específico que Ti para el argumento ei para todo i (1 ≤ i ≤ n, n = k)
Kirill Simonov
Debería estar arriba +1
0

porque el valor int también se puede considerar como doble en java. significa double a = 3es válido y lo mismo con el largo long b = 3Entonces, por eso está creando una ambigüedad. Llama

printSum(1, 2);

Es confuso para los tres métodos, porque todos estos tres son válidos:

int a = 1;
double b =1;
long c = 1;

Puede poner la L al final, para especificar que es un valor largo. por ejemplo:

printSum(1L, 2L);

para el doble necesitas convertirlo:

printSum((double)1, 2L);

También lea el comentario de @Erwin Bolwidt

Sandeep Kokate
fuente
Sí, el compilador se confunde con los 3 métodos, el primer compilador busca el tipo exacto y, si no encuentra ninguno, intente encontrar el tipo compatible y, en este caso, todos son compatibles.
Otro codificador
2
Si tengo 4 declaraciones de método en lugar de 3, la ambigüedad sale: public static void printSum (int a, double b) public static void printSum (int a, long b) public static void printSum (long a, long b) public static vacío printSum (doble a, largo b), así que aunque estoy de acuerdo con su respuesta, todavía no responde la pregunta. Java realiza la promoción automática de tipos si el tipo exacto no está disponible si no me equivoco. Simplemente no entiendo por qué se vuelve ambiguo con esos 3.
riruzen