Warum wählt die generische Art der Java 8-Inferenz diese Überladung aus?

33

Betrachten Sie das folgende Programm:

%Vor%

Es druckt "String" unter Java 8 und "Object" unter Java 7.

Ich hätte erwartet, dass dies in Java 8 eine Mehrdeutigkeit ist, weil beide überladenen Methoden übereinstimmen. Warum wählt der Compiler nach dem JEP 101 print(String) ?

Berechtigt oder nicht, dies bricht die Abwärtskompatibilität und die Änderung kann zur Kompilierzeit nicht erkannt werden. Der Code verhält sich nach dem Upgrade auf Java 8 nur schleichend.

HINWEIS: Das SillyGenericWrapper wird aus einem bestimmten Grund "albern" genannt. Ich versuche zu verstehen, warum der Compiler sich so verhält, sag mir nicht, dass der alberne Wrapper überhaupt ein schlechtes Design ist.

UPDATE: Ich habe auch versucht, das Beispiel unter Java 8 zu kompilieren und auszuführen, aber mit einem Java 7 Sprachlevel. Das Verhalten war konsistent mit Java 7. Das wurde erwartet, aber ich fühlte immer noch die Notwendigkeit zu verifizieren.

    
Bogdan Calmac 29.05.2015, 05:45
quelle

4 Antworten

18

Die Regeln der Typinferenz wurden in Java 8 grundlegend überarbeitet; am deutlichsten wurde die Zieltypinferenz deutlich verbessert. Während also vor Java 8 die Argumentseite der Methode keine Rückschlüsse auf das Objekt erhalten hat, wird in Java 8 der spezifischste anwendbare Typ abgeleitet, in diesem Fall String. Die JLS für Java 8 hat ein neues Kapitel Kapitel 18 eingeführt das fehlt in JLS für Java 7.

Frühere Versionen von JDK 1.8 (bis 1.8.0_25) hatten einen Fehler in Bezug auf die überladene Methodenauflösung, wenn der Compiler erfolgreich Code kompiliert hatte, der laut JLS zu einem Mehrdeutigkeitsfehler Warum ist diese Methode überladen mehrdeutig? Wie Marco13 in den Kommentaren betont

  

Dieser Teil der JLS ist wahrscheinlich der komplizierteste

erklärt die Fehler in früheren Versionen von JDK 1.8 und das Kompatibilitätsproblem, das Sie sehen.

Wie im Beispiel von Java Tutoral ( Typ-Inferenz ) gezeigt

  

Betrachten Sie die folgende Methode:

%Vor%
  

Angenommen, Sie möchten die Methode processStringList mit einer leeren Liste aufrufen. In Java SE 7 kompiliert die folgende Anweisung nicht:

%Vor%
  

Der Java SE 7-Compiler erzeugt eine Fehlermeldung ähnlich der folgenden:

%Vor%
  

Der Compiler benötigt einen Wert für das Typargument T, damit er mit dem Wert Object beginnt. Folglich gibt der Aufruf von Collections.emptyList einen Wert vom Typ List zurück, der mit der Methode processStringList inkompatibel ist. Daher müssen Sie in Java SE 7 den Wert des Werts des type-Arguments wie folgt angeben:

%Vor%
  

Dies ist in Java SE 8 nicht mehr erforderlich. Der Begriff des Zieltyps wurde um Methodenargumente wie das Argument für die Methode processStringList erweitert. In diesem Fall benötigt processStringList ein Argument vom Typ List

Collections.emptyList() ist eine generische Methode, die der Methode get() der Frage ähnlich ist. In Java 7 ist die print(String string) -Methode nicht einmal auf den Methodenaufruf anwendbar und nimmt daher nicht am Prozess der Überladungsauflösung teil . Während in Java 8 beide Methoden anwendbar sind.

Diese Inkompatibilität ist im Kompatibilitätshandbuch für JDK 8 .

Sie können meine Antwort auf eine ähnliche Frage im Zusammenhang mit der überladenen Methodenauflösung lesen Methode Überladung Ambiguität mit Java 8 ternäre bedingte und ungepanzte Primitive

Nach JLS 15.12.2.5 Die meisten auswählen Spezifische Methode :

  

Wenn mehrere Methoden gleichzeitig verfügbar und anwendbar sind a   Methodenaufruf ist es notwendig, einen zu wählen, um den   Deskriptor für den Laufzeitmethodenversand. Die Java-Programmierung   Sprache verwendet die Regel, dass die spezifischste Methode ausgewählt wird.

Dann:

  

Eine anwendbare Methode m1 ist spezifischer als eine andere anwendbar   Methode m2, für einen Aufruf mit Argumentausdrücken e1, ..., ek, if   Folgendes trifft zu:

     
  1. m2 ist generisch und m1 ist spezifischer als m2 für   Argumentausdrücke e1, ..., ek nach § 18.5.4.

  2.   
  3. m2 ist nicht generisch, und m1 und m2 sind strikt oder lose anwendbar   Aufruf, und wo m1 formale Parameter Typen S1, ..., Sn und m2 hat   hat formale Parametertypen T1, ..., Tn, der Typ Si ist spezifischer   als Ti für Argument ei für alle i (1 ≤ i ≤ n, n = k).

  4.   
  5. m2 ist nicht generisch, und m1 und m2 sind variabel anwendbar   Aufruf, und wo die ersten k Variable Arity Parametertypen von m1   sind S1, ..., Sk und die ersten k variablen Parameter-Typen von m2   sind T1, ..., Tk, der Typ Si ist spezifischer als Ti für Argument ei   für alle i (1 ≤ i ≤ k). Wenn m2 k + 1 Parameter hat, dann   Der k + 1-te variable Arityparametertyp von m1 ist ein Untertyp des   k + 1. Variable Variable Parameter Typ von m2.

  6.   

Die obigen Bedingungen sind die einzigen Umstände, unter denen eine Methode spezifischer sein kann als eine andere.

     

Ein Typ S ist spezifischer als ein Typ T für irgendeinen Ausdruck, wenn S & lt; T (§4.10).

Die zweite von drei Optionen entspricht unserem Fall. Da String ein Untertyp von Object ( String <: Object ) ist, ist es spezifischer. Daher ist die Methode selbst spezifischer . Nach der JLS ist diese Methode auch streng spezifischer und spezifischste und wird vom Compiler gewählt.

    
medvedev1088 29.05.2015, 10:11
quelle
6

In java7 werden Ausdrücke von unten interpretiert (mit sehr wenigen Ausnahmen); Die Bedeutung eines Unterausdrucks ist eine Art von "kontextfrei". Bei einem Methodenaufruf werden die Typen der Argumente zuerst aufgelöst. Der Compiler verwendet diese Informationen dann, um die Bedeutung des Aufrufs aufzulösen, z. B. um einen Gewinner unter den anwendbaren überladenen Methoden auszuwählen.

In java8 funktioniert diese Philosophie nicht mehr, weil wir erwarten, implizites Lambda (wie x->foo(x) ) überall zu verwenden; Die Lambda-Parametertypen sind nicht spezifiziert und müssen aus dem Kontext abgeleitet werden. Das heißt, bei Methodenaufrufen entscheiden manchmal die Methodenparametertypen über die Argumenttypen.

Offensichtlich gibt es ein Dilemma, wenn die Methode überladen ist. Daher ist es in einigen Fällen erforderlich, das Methodenüberladen zuerst zu lösen, um einen Gewinner auszuwählen, bevor die Argumente kompiliert werden.

Das ist eine große Veränderung; und ein alter Code wie der Ihre wird der Inkompatibilität zum Opfer fallen.

Eine Problemumgehung besteht darin, dem Argument eine "Zieleingabe" mit "Casting-Kontext" zu geben

%Vor%

oder like @ Holger's Vorschlag, geben Sie den Parameter <Object>get() ein, um den Schluss zu vermeiden.

Das Überladen von Java-Methoden ist extrem kompliziert; der Vorteil der Komplexität ist zweifelhaft. Denken Sie daran, Überladung ist nie eine Notwendigkeit - wenn sie unterschiedliche Methoden sind, können Sie ihnen verschiedene Namen geben.

    
ZhongYu 29.05.2015 15:02
quelle
2

Erstens hat das nichts mit Übersteuerung zu tun, aber es muss mit Überladung umgehen.

Jls ,. Abschnitt 15 enthält viele Informationen darüber, wie genau der Compiler die überladene Methode auswählt

  

Die spezifischste Methode wird zur Kompilierzeit gewählt; sein Deskriptor   bestimmt, welche Methode zur Laufzeit tatsächlich ausgeführt wird.

Also beim Aufruf

%Vor%

Der Compiler wählt String version über Object , weil print Methode, die String benötigt, ist spezifischer als die, die Object benötigt. Wenn Integer anstelle von String vorhanden ist, wird es ausgewählt.

Wenn Sie außerdem eine Methode aufrufen möchten, die Object als Parameter verwendet, können Sie den Rückgabewert dem Parameter% object zB.

zuweisen %Vor%

Es gibt

aus %Vor%

Die Situation wird interessant, wenn Sie sagen, Sie haben zwei gültige Methodendefinitionen, die für eine Überladung in Frage kommen. ZB

%Vor%

und jetzt, wenn Sie

aufrufen %Vor%

Der Compiler wird 2 gültige Methodendefinitionen zur Auswahl haben. Daher erhalten Sie einen Kompilierungsfehler, da er keine Methode gegenüber der anderen bevorzugen kann.

    
sol4me 29.05.2015 07:45
quelle
1

Ich habe es mit Java 1.8.0_40 ausgeführt und habe "Object" erhalten.

Wenn Sie den folgenden Code ausführen:

%Vor%

Sie werden sehen, dass Sie bekommen:

  

Objekt öffentlich T   com.xxx.GenericTypeInference $ SillyGenericWrapper.get ()                 ReturnType: Klasse java.lang.Object          GenericReturnType: T

Dies erklärt, warum die mit Object überladene Methode verwendet wird und nicht die String-Methode.

    
Daniel L. 29.05.2015 06:26
quelle