Promoção do tipo Java em parâmetros

20

Eu tropecei neste trecho:

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);
    }
}

Isso resultará em um erro de compilação:

Erro: (15, 9) java: a referência a printSum é ambígua, tanto o método printSum (int, double) no ParamTest quanto o método printSum (long, long) na correspondência do ParamTest

Como isso é ambíguo? Nesse caso, não deve ser promovido apenas o segundo parâmetro, pois o primeiro parâmetro já é um int? O primeiro parâmetro não precisa ser promovido neste caso, certo?

A compilação terá êxito se eu atualizar o código para adicionar outro método:

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

Deixe-me expandir apenas para esclarecer. O código abaixo resulta em ambiguidade:

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);
    }
}

Então este código abaixo também resulta em ambiguidade:

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);
    }
}

No entanto, este não resulta em ambiguidade:

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
fonte
2
O compilador pode mapear sua chamada printSum (1, 2); para printSum (long a, long b) ou printSum (int a, double b), portanto, é ambíguo. Você tem que ajudar explicitamente o compilador para escolher entre, dando o tipo precisamente assim: printSum (1, 2-D)
Ravindra Ranwala
6
Você citou incorretamente a mensagem de erro e a diferença é muito importante. A mensagem de erro real é: Error:(15, 9) java: reference to printSum is ambiguous both method printSum(int,double) in ParamTest and method printSum(long,long) in ParamTest match- não é o método ambíguo, é a chamada para o método ambíguo.
Erwin Bolwidt 15/10/19
11
@ErwinBolwidt a mensagem de erro era do eclipse, desculpe, eu citei lá. de qualquer forma, ainda não entendi porque adicionar printSum (int a, long b) remove a mensagem de erro.
riruzen
2
JLS-5.3 => Se o tipo da expressão não puder ser convertido no tipo do parâmetro por uma conversão permitida em um contexto de invocação livre, ocorrerá um erro em tempo de compilação. parece ser aplicável ao contexto, mas não é realmente direto inferir como. +1
Naman
11
Definitivamente, precisamos de uma pergunta canônica para isso: stackoverflow.com/… . "
Marco13

Respostas:

17

Eu acho que isso tem algo a ver com a regra específica do JLS sobre 15.12.2.5. Escolhendo o método mais específico . Diz que:

Se mais de um método membro for acessível e aplicável a uma chamada de método, será necessário escolher um para fornecer o descritor para o envio do método em tempo de execução. A linguagem de programação Java usa a regra de que o método mais específico é escolhido.

Como o Java escolhe o método mais específico é explicado mais detalhadamente pelo texto:

A intuição informal é que um método é mais específico que outro, se qualquer chamada manipulada pelo primeiro método puder ser passada para o outro sem um erro em tempo de compilação. Em casos como um argumento de expressão lambda explicitamente digitado (§15.27.1) ou uma chamada de variável variável (§15.12.2.4), é permitida alguma flexibilidade para adaptar uma assinatura à outra.

No caso do seu exemplo, todos os métodos são acessíveis e aplicáveis ​​à invocação de métodos; portanto, o Java precisa determinar qual deles é mais específico .

Para esses métodos, nenhum pode ser determinado como mais 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

O quarto método elimina a ambiguidade precisamente porque preenche a condição necessária para ser mais específico .

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

Ou seja, (int, long) pode ser passado para (int, double), (long, long) ou (double, long) sem erros de compilação.

tumulto
fonte
2
Então, para o verão, se todos os métodos são acessíveis pela chamada, o java identifica o método, que pode ser chamado para outros métodos sem erro de tempo de compilação, se encontrado, use-o como mais específico e, caso contrário, chamará de erro de ambiguidade? Eu acho que essa é uma resposta válida @riruzen.
Sandeep Kokate 15/10/19
Obrigado! Eu tentei isso com (double, int) vs (double, long) e realmente esclareceu a ambiguidade, então (double, int) vs (long, double) ficou ambíguo conforme o esperado.
Rindouzen 15/10/19
7

É realmente uma pergunta muito interessante. Vamos examinar a Especificação de Linguagem Java passo a passo.

  1. Quando o compilador está tentando identificar métodos potencialmente aplicáveis, a primeira coisa que faz é procurar métodos aplicáveis ​​pela Invocação Restrita .

  2. No seu caso, não existem métodos desse tipo, portanto, a próxima etapa é encontrar métodos aplicáveis ​​pela Loose Invocation

  3. Nesse ponto, todos os métodos coincidem; portanto, o método mais específico ( §15.12.2.5 ) é escolhido entre os métodos aplicáveis ​​por invocação solta.

Este é um momento chave, então vamos ver isso de perto.

Um método aplicável m1 é mais específico que outro método aplicável m2, para uma chamada com expressões de argumento e1, ..., ek, se alguma das seguintes opções for verdadeira:

(Estamos interessados ​​apenas no seguinte caso):

  • m2 não é genérico, e m1 e m2 são aplicáveis ​​por chamada estrita ou frouxa, e onde m1 possui tipos de parâmetros formais S1, ..., Sn e m2 possui tipos de parâmetros formais T1, ..., Tn, o tipo Si é mais específico que Ti para o argumento ei para todos os i (1 ≤ i ≤ n, n = k).

Simplificando, um método é mais específico se todos os seus tipos de parâmetros forem mais específicos . E

Um tipo S é mais específico que um tipo T para qualquer expressão se S <: T ( §4.10 ).

Expressão S <: Tsignifica que Sé um subtipo de T. Para primitivos, temos o seguinte relacionamento:

double > float > long > int

Então, vejamos seus métodos e vejamos qual é mais específico que outros.

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));
}

Neste exemplo, o primeiro parâmetro do método 1 é obviamente mais específico que o primeiro parâmetro do método 2 (se você os chamar com valores inteiros:) printSum(1, 2). Mas o segundo parâmetro é mais específico para o método 2 , porque long < double. Portanto, nenhum desses métodos é mais específico que outro. É por isso que você tem uma ambiguidade aqui.

No exemplo a seguir:

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));
}

o primeiro tipo de parâmetro do método 1 é mais específico que o método 2, porque int < longe o segundo tipo de parâmetro é o mesmo para os dois, é por isso que o método 1 é escolhido.

Kirill Simonov
fonte
Não diminuí a votação da sua resposta, mas essa resposta não explica por que (int, double) é ambígua com (long, long), uma vez que claramente (int, double) deve ser mais específico em relação ao primeiro parâmetro.
Rindouzen 15/10/19
3
@ruzuzen explica: doublenão é mais específico do que long. E para o método a ser escolhido, todos os parâmetros de tipo devem ser mais específicos: o tipo Si é mais específico do que Ti para o argumento ei para todos os i (1 ≤ i ≤ n, n = k)
Kirill Simonov
Deve ser +1
Tea
0

porque o valor int também pode ser considerado como duplo em java. meios double a = 3é válido e o mesmo com o longo long b = 3É por isso que está criando uma ambiguidade. Você chama

printSum(1, 2);

É confuso para todos os três métodos, porque todos esses três são válidos:

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

Você pode colocar L no final, para especificar que é um valor longo. por exemplo:

printSum(1L, 2L);

para o dobro, você precisa convertê-lo:

printSum((double)1, 2L);

leia também o comentário do @Erwin Bolwidt

Sandeep Kokate
fonte
Sim, o compilador está confuso para todos os três métodos, o primeiro compilador procura o tipo exato e, se não encontrou nenhum, tente encontrar o tipo compatível e, nesse caso, todos são compatíveis.
Outro codificador
2
Se eu tiver 4 declarações de método em vez de 3, a ambiguidade sairá: 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 void printSum (double a, long b), portanto, embora eu concorde com sua resposta, ela ainda não responde à pergunta. Java faz promoção automática de tipo se o tipo exato não estiver disponível se não me engano. Eu simplesmente não entendo por que isso se torna ambíguo com aqueles 3.
riruzen 15/10/1919