Java-Typ-Promotion in Parametern

20

Ich bin auf diesen Ausschnitt gestoßen:

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

Dies führt zu einem Kompilierungsfehler:

Fehler: (15, 9) Java: Der Verweis auf printSum ist mehrdeutig, sowohl die Methode printSum (int, double) in ParamTest als auch die Methode printSum (long, long) in ParamTest stimmen überein

Wie ist das mehrdeutig? Sollte in diesem Fall nicht nur der zweite Parameter heraufgestuft werden, da der erste Parameter bereits ein int ist? Der erste Parameter muss in diesem Fall nicht befördert werden, oder?

Die Kompilierung ist erfolgreich, wenn ich den Code aktualisiere, um eine weitere Methode hinzuzufügen:

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

Lassen Sie mich nur zur Klarstellung erweitern. Der folgende Code führt zu Mehrdeutigkeiten:

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

Dann führt dieser Code unten auch zu Mehrdeutigkeiten:

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

Dies führt jedoch nicht zu Mehrdeutigkeiten:

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
quelle
2
Der Compiler kann Ihren Aufruf printSum (1, 2) zuordnen. entweder printSum (long a, long b) oder printSum (int a, double b) ist daher nicht eindeutig. Sie müssen dem Compiler ausdrücklich bei der Auswahl helfen, indem Sie den Typ genau so angeben: printSum (1, 2d)
Ravindra Ranwala
6
Sie haben die Fehlermeldung falsch zitiert, und der Unterschied ist sehr wichtig. Die eigentliche Fehlermeldung lautet: Error:(15, 9) java: reference to printSum is ambiguous both method printSum(int,double) in ParamTest and method printSum(long,long) in ParamTest match- Es ist nicht die Methode, die mehrdeutig ist, sondern der Aufruf der Methode, die nicht eindeutig ist.
Erwin Bolwidt
1
@ErwinBolwidt Die Fehlermeldung war von Eclipse. Entschuldigung, ich habe dort falsch zitiert. Trotzdem verstehe ich es immer noch nicht, weil das Hinzufügen von printSum (int a, long b) die Fehlermeldung entfernt.
Riruzen
2
JLS-5.3 => Wenn der Typ des Ausdrucks durch eine in einem losen Aufrufkontext zulässige Konvertierung nicht in den Typ des Parameters konvertiert werden kann, tritt ein Fehler bei der Kompilierung auf. scheint auf den Kontext anwendbar zu sein, aber nicht wirklich einfach zu schließen, wie. +1
Naman
1
Wir brauchen definitiv eine kanonische Frage dafür: stackoverflow.com/… . "
Marco13

Antworten:

17

Ich denke, das hat etwas mit der spezifischen Regel von JLS vom 15.12.2.5 zu tun . Auswahl der spezifischsten Methode . Es sagt, dass:

Wenn mehr als eine Mitgliedsmethode für einen Methodenaufruf zugänglich und anwendbar ist, muss eine ausgewählt werden, um den Deskriptor für den Versand der Laufzeitmethode bereitzustellen. Die Programmiersprache Java verwendet die Regel, dass die spezifischste Methode ausgewählt wird.

Wie Java die spezifischste Methode auswählt , wird im Text näher erläutert:

Die informelle Intuition ist, dass eine Methode spezifischer als eine andere ist, wenn ein Aufruf, der von der ersten Methode verarbeitet wird, ohne einen Fehler bei der Kompilierung an die andere Methode weitergegeben werden könnte. In Fällen wie einem explizit typisierten Lambda-Ausdrucksargument (§15.27.1) oder einem Aufruf einer variablen Arität (§15.12.2.4) ist eine gewisse Flexibilität zulässig, um eine Signatur an die andere anzupassen.

In Ihrem Beispiel sind alle Methoden zugänglich und auf den Methodenaufruf anwendbar. Daher muss Java bestimmen, welche davon am spezifischsten ist .

Für diese Methoden kann keine spezifischer bestimmt werden:

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

Die vierte Methode löscht Mehrdeutigkeiten genau deshalb, weil sie die notwendige Bedingung erfüllt, um am spezifischsten zu sein .

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

Das heißt, (int, long) kann ohne Kompilierungsfehler an (int, double), (long, long) oder (double, long) übergeben werden.

randalieren
quelle
2
Wenn also alle Methoden über den Aufruf zugänglich sind, identifiziert Java die Methode, die ohne Kompilierungszeitfehler für andere Methoden aufgerufen werden kann. Wenn sie gefunden wird, verwenden Sie diese als die spezifischste und nennen Sie sie ansonsten Mehrdeutigkeitsfehler. Ich denke, dies ist eine gültige Antwort @riruzen.
Sandeep Kokate
Vielen Dank! Ich habe dies mit (double, int) vs (double, long) versucht und es hat wirklich die Mehrdeutigkeit geklärt, dann war (double, int) vs (long, double) wie erwartet mehrdeutig.
Riruzen
7

Das ist in der Tat eine sehr interessante Frage. Lassen Sie uns Schritt für Schritt die Java-Sprachspezifikation durchgehen.

  1. Wenn der Compiler versucht, potenziell anwendbare Methoden zu identifizieren, sucht er zunächst nach Methoden, die durch Strict Invocation anwendbar sind .

  2. In Ihrem Fall gibt es keine solchen Methoden. Der nächste Schritt besteht darin , Methoden zu finden, die für Loose Invocation anwendbar sind

  3. Zu diesem Zeitpunkt stimmen alle Methoden überein, sodass die spezifischste Methode ( §15.12.2.5 ) unter den Methoden ausgewählt wird, die durch losen Aufruf anwendbar sind.

Dies ist ein Schlüsselmoment, also schauen wir uns das genauer an.

Eine anwendbare Methode m1 ist spezifischer als eine andere anwendbare Methode m2 für einen Aufruf mit den Argumentausdrücken e1, ..., ek, wenn eine der folgenden Aussagen zutrifft:

(Wir interessieren uns nur für den folgenden Fall):

  • m2 ist nicht generisch, und m1 und m2 sind durch strikten oder losen Aufruf anwendbar, und wo m1 formale Parametertypen S1, ..., Sn und m2 formale Parametertypen T1, ..., Tn hat, ist der Typ Si mehr spezifisch als Ti für das Argument ei für alle i (1 ≤ i ≤ n, n = k).

Einfach ausgedrückt ist eine Methode spezifischer, wenn alle Parametertypen spezifischer sind . Und

Ein Typ S ist für jeden Ausdruck spezifischer als ein Typ T, wenn S <: T ( §4.10 ).

Ausdruck S <: Tbedeutet, dass dies Sein Subtyp von ist T. Für Primitive haben wir folgende Beziehung:

double > float > long > int

Schauen wir uns also Ihre Methoden an und sehen, welche spezifischer ist als andere.

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

In diesem Beispiel ist der erste Parameter der Methode 1 offensichtlich spezifischer als der erste Parameter der Methode 2 (wenn Sie sie mit ganzzahligen Werten aufrufen :) printSum(1, 2). Der zweite Parameter ist jedoch spezifischer für die Methode 2 , weil long < double. Keine dieser Methoden ist spezifischer als die andere. Deshalb haben Sie hier eine Mehrdeutigkeit.

Im folgenden Beispiel:

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

Der erste Parametertyp der Methode 1 ist spezifischer als der in Methode 2, da int < longder zweite Parametertyp für beide gleich ist. Deshalb wird die Methode 1 ausgewählt.

Kirill Simonov
quelle
Ich habe Ihre Antwort nicht abgelehnt, aber diese Antwort erklärt nicht, warum (int, double) mit (long, long) nicht eindeutig ist, da (int, double) in Bezug auf den ersten Parameter eindeutig spezifischer sein sollte.
Riruzen
3
@riruzen erklärt es: doubleist nicht spezifischer als long. Und damit die Methode gewählt werden kann, müssen alle Typparameter spezifischer sein: Typ Si ist spezifischer als Ti für das Argument ei für alle i (1 ≤ i ≤ n, n = k)
Kirill Simonov
Es sollte +1 sein
Tee
0

weil der int-Wert in Java auch als double betrachtet werden kann. Mittel double a = 3ist gültig und gleich mit dem langen. long b = 3Deshalb schafft es eine Mehrdeutigkeit. Du rufst an

printSum(1, 2);

Ist für alle drei Methoden verwirrend, da alle drei gültig sind:

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

Sie können das L am Ende setzen, um anzugeben, dass es ein langer Wert ist. zum Beispiel:

printSum(1L, 2L);

für double musst du es konvertieren:

printSum((double)1, 2L);

Lesen Sie auch den Kommentar von @Erwin Bolwidt

Sandeep Kokate
quelle
Ja, der Compiler ist für alle drei Methoden verwirrt. Der erste Compiler sucht nach dem genauen Typ. Wenn er keinen findet, versuchen Sie, den kompatiblen Typ zu finden. In diesem Fall sind alle kompatibel.
Ein weiterer Codierer
2
Wenn ich 4 Methodendeklarationen anstelle von 3 habe, wird die Mehrdeutigkeit aufgehoben: 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) Obwohl ich Ihrer Antwort zustimme, wird die Frage immer noch nicht beantwortet. Java führt eine automatische Typ-Promotion durch, wenn der genaue Typ nicht verfügbar ist, wenn ich mich nicht irre. Ich verstehe nur nicht, warum es mit diesen 3.
riruzen