Promocja typu Java w parametrach

20

Natknąłem się na ten fragment:

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

Spowoduje to błąd kompilacji:

Błąd: (15, 9) java: odwołanie do printSum jest niejednoznaczne, zarówno metoda printSum (int, double) w ParamTest, jak i metoda printSum (długa, długa) w ParamTest

Jak to jest niejednoznaczne? Czy w tym przypadku nie należy promować tylko drugiego parametru, ponieważ pierwszy parametr jest już liczbą całkowitą? Pierwszy param nie musi być promowany w tym przypadku, prawda?

Kompilacja powiedzie się, jeśli zaktualizuję kod, aby dodać inną metodę:

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

Pozwól, że rozwinę się, aby wyjaśnić. Poniższy kod powoduje niejednoznaczność:

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

Zatem poniższy kod również powoduje niejednoznaczność:

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

Jednak nie powoduje to dwuznaczności:

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
źródło
2
Kompilator może zmapować połączenie printSum (1, 2); do printSum (długi a, długi b) lub printSum (int a, double b) stąd jest niejednoznaczny. Musisz wyraźnie pomóc kompilatorowi w wyborze, podając typ dokładnie taki: printSum (1, 2d)
Ravindra Ranwala
6
Błędnie zacytowałeś komunikat o błędzie, a różnica jest bardzo ważna. Rzeczywistym komunikatem o błędzie jest: Error:(15, 9) java: reference to printSum is ambiguous both method printSum(int,double) in ParamTest and method printSum(long,long) in ParamTest match- to nie jest metoda dwuznaczna, to wywołanie metody niejednoznacznej.
Erwin Bolwidt
1
@ErwinBolwidt komunikat o błędzie pochodzi z zaćmienia, przepraszam, że tam źle napisałem. tak czy inaczej, nadal tego nie rozumiem, ponieważ dodanie printSum (int a, long b) usuwa komunikat o błędzie.
riruzen
2
JLS-5.3 => Jeśli typu wyrażenia nie można przekonwertować na typ parametru przez konwersję dozwoloną w kontekście luźnego wywołania, wówczas występuje błąd czasu kompilacji. wydaje się mieć zastosowanie do kontekstu, ale nie jest tak proste, aby wywnioskować, w jaki sposób. +1
Naman
1
Zdecydowanie potrzebujemy w tym celu pytania kanonicznego: stackoverflow.com/… . ”
Marco13

Odpowiedzi:

17

Myślę, że ma to związek z konkretną regułą JLS dotyczącą 15.12.2.5. Wybór najbardziej szczegółowej metody . Twierdzi, że:

Jeśli więcej niż jedna metoda elementu jest zarówno dostępna, jak i ma zastosowanie do wywołania metody, konieczne jest wybranie jednej z nich, aby zapewnić deskryptor dla wysyłki metody w czasie wykonywania. Język programowania Java korzysta z reguły wyboru najbardziej szczegółowej metody.

Sposób, w jaki Java wybiera najbardziej szczegółową metodę, wyjaśniono w dalszej części tekstu:

Nieformalna intuicja polega na tym, że jedna metoda jest bardziej szczegółowa niż inna, jeśli jakiekolwiek wywołanie obsługiwane przez pierwszą metodę może zostać przekazane drugiej bez błędu kompilacji. W przypadkach takich jak jawnie wpisany argument wyrażenia lambda (§15.27.1) lub wywołanie zmiennej arity (§15.12.2.4), dopuszcza się pewną elastyczność w dostosowywaniu jednej sygnatury do drugiej.

W naszym przykładzie wszystkie metody są dostępne i mają zastosowanie do wywoływania metod, dlatego Java musi określić, która z nich jest najbardziej specyficzna .

W przypadku tych metod nie można określić bardziej szczegółowej:

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

Czwarta metoda usuwa niejasności właśnie dlatego, że spełnia warunek konieczny, aby być najbardziej szczegółowym .

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

Oznacza to, że (int, long) można przekazać do (int, double), (long, long) lub (double, long) bez błędów kompilacji.

zamieszki
źródło
2
Tak więc, aby przejść do trybu letniego, jeśli wszystkie metody są dostępne przez wywołanie, to java identyfikuje metodę, która może być wywołana do innych metod bez błędu czasu kompilacji, jeśli zostanie znaleziona, użyj jej jako najbardziej specyficznej i nazwij ją inaczej błąd niejednoznaczności? Myślę, że to poprawna odpowiedź @ riruzen.
Sandeep Kokate
Dzięki! Próbowałem tego z (double, int) vs (double, long) i naprawdę wyjaśniło to niejednoznaczność, a następnie (double, int) vs (long, double) było niejednoznaczne, jak oczekiwano.
riruzen
7

To jest naprawdę bardzo interesujące pytanie. Przejdźmy krok po kroku do specyfikacji języka Java.

  1. Kiedy kompilator próbuje zidentyfikować potencjalnie odpowiednie metody, pierwszą rzeczą, jaką robi, jest wyszukiwanie metod stosowanych przez Strict Invocation .

  2. W twoim przypadku nie ma takich metod, więc następnym krokiem jest znalezienie metod mających zastosowanie w przypadku luźnego wywołania

  3. W tym momencie wszystkie metody się zgadzają, więc najbardziej szczegółowa metoda ( §15.12.2.5 ) jest wybierana spośród metod, które można zastosować w drodze luźnego wywołania.

To kluczowy moment, więc przyjrzyjmy się temu z bliska.

Jedna mająca zastosowanie metoda m1 jest bardziej szczegółowa niż inna mająca zastosowanie metoda m2, dla wywołania za pomocą wyrażeń argumentowych e1, ..., ek, jeśli spełniony jest dowolny z poniższych warunków:

(Interesuje nas tylko następujący przypadek):

  • m2 nie jest ogólne, a m1 i m2 mają zastosowanie przy użyciu ścisłego lub luźnego wywołania, a gdy m1 ma formalne typy parametrów S1, ..., Sn i m2 ma formalne typy parametrów T1, ..., Tn, typ Si jest bardziej specyficzny niż Ti dla argumentu ei dla wszystkich i (1 ≤ i ≤ n, n = k).

Mówiąc najprościej, metoda jest bardziej szczegółowa, jeśli wszystkie jej typy parametrów są bardziej szczegółowe . I

Typ S jest bardziej szczegółowy niż typ T dla dowolnego wyrażenia, jeśli S <: T ( §4.10 ).

Wyrażenie S <: Toznacza, że Sjest to podtyp T. W przypadku prymitywów mamy następujący związek:

double > float > long > int

Spójrzmy więc na twoje metody i zobacz, która z nich jest bardziej szczegółowa niż inne.

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

W tym przykładzie pierwszy parametr metody 1 jest oczywiście bardziej szczegółowy niż pierwszy parametr metody 2 (jeśli wywołasz je z wartościami całkowitymi:) printSum(1, 2). Ale drugi parametr jest bardziej szczegółowy dla metody 2 , ponieważ long < double. Żadna z tych metod nie jest bardziej szczegółowa niż inna. Dlatego masz tu dwuznaczność.

W poniższym przykładzie:

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

pierwszy typ parametru w metodzie 1 jest bardziej szczegółowy niż ten w metodzie 2, ponieważ int < longi drugi typ parametru jest taki sam dla obu z nich, dlatego wybrano metodę 1.

Kirill Simonov
źródło
Nie głosowałem za twoją odpowiedzią, ale ta odpowiedź nie wyjaśnia, dlaczego (int, double) jest niejednoznaczny z (long, long), ponieważ wyraźnie (int, double) powinien być bardziej szczegółowy w odniesieniu do pierwszego parametru.
riruzen
3
@riruzen wyjaśnia: doublenie jest bardziej szczegółowy niż long. Aby wybrać metodę, wszystkie parametry typu muszą być bardziej szczegółowe: typ Si jest bardziej szczegółowy niż Ti dla argumentu ei dla wszystkich i (1 ≤ i ≤ n, n = k)
Kirill Simonov
Powinno wzrosnąć +1
Herbata
0

ponieważ wartość int może być również uważana za podwójną w java. środki double a = 3są ważne i to samo z długim long b = 3Więc dlatego tworzy niejednoznaczność. Ty dzwonisz

printSum(1, 2);

Jest mylący dla wszystkich trzech metod, ponieważ wszystkie te trzy są poprawne:

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

Możesz umieścić L na końcu, aby określić, że jest to długa wartość. na przykład:

printSum(1L, 2L);

podwójnie musisz go przekonwertować:

printSum((double)1, 2L);

przeczytaj także komentarz @Erwin Bolwidt

Sandeep Kokate
źródło
Tak, kompilator mylony dla wszystkich 3 metod, pierwszy kompilator szuka dokładnego typu, a jeśli go nie znalazł, spróbuj znaleźć kompatybilny typ, aw tym przypadku wszystkie są kompatybilne.
Kolejny koder
2
Jeśli mam 4 deklaracje metod zamiast 3, dwuznaczność znika: 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 (podwójne a, długie b), więc chociaż zgadzam się z twoją odpowiedzią, nadal nie odpowiada na pytanie. Java dokonuje automatycznej promocji typu, jeśli dokładny typ nie jest dostępny, jeśli się nie mylę. Po prostu nie rozumiem, dlaczego staje się niejednoznaczny z tymi 3.
riruzen