2015-02-11 9 views
21

Nehmen wir an, wir haben 2 Klassen. Eine leere Klasse Base und eine Unterklasse dieser Klasse Derived.Java-Typ-Inferenz: Verweis ist mehrdeutig in Java 8, aber nicht Java 7

public class Base {} 

public class Derived extends Base {} 

Dann haben wir ein paar Methoden in einer anderen Klasse:

import java.util.Collection 

public class Consumer { 

    public void test() { 
     set(new Derived(), new Consumer().get()); 
    } 

    public <T extends Base> T get() { 
     return (T) new Derived(); 
    } 

    public void set(Base i, Derived b) { 
     System.out.println("base"); 
    } 

    public void set(Derived d, Collection<? extends Consumer> o) { 
     System.out.println("object"); 
    } 

} 

Dies kompiliert und läuft erfolgreich in Java 7, aber nicht kompiliert in Java 8. Der Fehler:

Error:(8, 9) java: reference to set is ambiguous 
    both method set(Base,Derived) in Consumer and 
    method set(Derived,java.util.Collection) in Consumer match 

Warum funktioniert in Java 7, aber nicht Java 8? Wie könnte <T extends Base>jemals übereinstimmen Sammlung?

+0

Nun, es kompiliert für mich gut mit Java 8 – Dici

+0

Eclipse kompiliert dies, aber 'javac' kann nicht. Ist es sicher anzunehmen, dass Sie 'javac' verwenden? – Jeffrey

+0

@ Jeffrey Ja. Ich bekomme diesen Fehler Kompilierung von der Kommandozeile mit javac und mit IntelliJ. Ich bin mit Java-Version 1.8.0_25 –

Antwort

22

Das Problem ist, dass die Art Rückschluss verbessert wurde. Sie haben eine Methode, wie

public <T extends Base> T get() { 
    return (T) new Derived(); 
} 

die im Grunde sagt: „der Anrufer entscheiden können, welche Unterklasse von Base ich zurückkehren“, was offensichtlich ist Unsinn. Jeder Compiler sollte Ihnen hier eine unkontrollierte Warnung über Ihren Typcast (T) geben.

Jetzt haben Sie einen Methodenaufruf:

set(new Derived(), new Consumer().get()); 

Daran erinnern, dass Ihre Methode Consumer.get() sagt „der Anrufer entscheiden kann, was ich zurückkehren“. Es ist also vollkommen richtig anzunehmen, dass es einen Typ geben könnte, der Base erweitert und gleichzeitig Collection implementiert. Der Compiler sagt also "Ich weiß nicht, ob ich set(Base i, Derived b) oder set(Derived d, Collection<? extends Consumer> o) anrufen soll".

Sie können „reparieren“ durch set(new Derived(), new Consumer().<Derived>get()); ruft aber den Wahnsinn Ihrer Methode zu veranschaulichen, beachten Sie, dass Sie es auch zu

ändern
public <X extends Base&Collection<Consumer>> void test() { 
    set(new Derived(), new Consumer().<X>get()); 
} 

die jetzt set(Derived d, Collection<? extends Consumer> o) ohne Compiler-Warnung nennen. Die eigentliche unsichere Operation ist innerhalb der get Methode passiert.

Also die richtigen fix wäre, die Typ-Parameter aus dem get Verfahren zu entfernen und erklären, was es wirklich zurückkehrt, Derived.


By the way, was mich ärgert, ist Ihr Anspruch, dass dieser Code unter Java 7. Seine begrenzte Typinferenz mit verschachtelten Methodenaufrufe führt kompiliert werden, um die get Methode in einem verschachtelten Aufrufkontext zu behandeln wie Base Rückkehr welches nicht an eine Methode übergeben werden kann, die eine Derived erwartet. Daher wird auch der Versuch, diesen Code mit einem konformen Java 7-Compiler zu kompilieren, aus verschiedenen Gründen fehlschlagen.

5

Nun, es ist nicht so, als wäre Java7 glücklich, es zu betreiben.Es gibt einige Warnungen vor den Fehler geben:

[email protected]~$ javac -Xlint:unchecked -source 1.7 com/company/Main.java 
warning: [options] bootstrap class path not set in conjunction with -source 1.7 
com/company/Main.java:19: error: no suitable method found for set(Derived,Base) 
     set(new Derived(), new Consumer().get()); 
     ^
    method Consumer.set(Base,Derived) is not applicable 
     (argument mismatch; Base cannot be converted to Derived) 
    method Consumer.set(Derived,Collection<? extends Consumer>) is not applicable 
     (argument mismatch; Base cannot be converted to Collection<? extends Consumer>) 

com/company/Main.java:28: warning: [unchecked] unchecked cast 
     return (T) new Derived(); 
       ^
    required: T 
    found: Derived 
    where T is a type-variable: 
    T extends Base declared in method <T>get() 

Das Problem ist folgendes:

set(new Derived(), new Consumer().get()); 

Wenn wir das tun new Consumer().get(), wenn wir bei Unterzeichnung get aussehen. Es gibt uns einen Typ T zurück. Wir wissen, dass T irgendwie erweitert Base. Aber wir wissen nicht, was T speziell ist. Es kann alles sein. Also, wenn wir nicht genau entscheiden können, was T ist, dann wie kann Compiler?

Eine Möglichkeit, den Compiler zu unterscheiden, ist das Hardcoding und das genaue Abgeben von: set(new Derived(), new Consumer().<Derived>get());.

Die oben genannten Grund ist extrem extrem (wiederholt absichtlich) gefährlich ist, wenn Sie versuchen, diese zu tun:

class NewDerived extends Base { 
    public String getName(){return "name";}; 
} 
NewDerived d = new Consumer().<NewDerived>get(); 
System.out.println(d.getName()); 

In Java7 (oder jede Java-Version), wird es Ausnahme zur Laufzeit werfen:

Exception in thread "main" java.lang.ClassCastException: com.company.Derived cannot be cast to com.company.NewDerived

Weil get ein Objekt vom Typ Derived zurückgibt, aber Sie haben zum Compiler erwähnt, dass es von NewDerived ist. Und er kann Derived nicht zu NewDerived konvertieren. Aus diesem Grund wird eine Warnung angezeigt.


Gemäß dem Fehler, jetzt verstehen wir, was mit new Consumer().get() falsch ist. Es ist Typ something that extends base. Doing set(new Derived(), new Consumer().get()); sucht nach einer Methode, die Argumente als Derived (or any super class of it), something that extends Base akzeptiert.

Jetzt sind beide Methoden für das erste Argument geeignet. Nach dem zweiten Argument something that extends base, wieder beide sind als geeignet, dass etwas abgeleitet werden kann oder erweitert oder erweitern Sammlung. Aus diesem Grund gibt Java8 Fehler aus.

Gemäß Java7 ist die Art der Inferenz etwas schwächer. So versucht sie, etwas zu tun, ähnlich,

Base base = new Consumer().get(); 
set(new Derived(), base); 

Und wieder kann es nicht die richtige Methode finden, die Derived, Base als Argument nimmt. Daher wirft es Fehler aber aus unterschiedlichen Gründen:

set(new Derived(), new Consumer().get()); 
    ^
method Consumer.set(Base,Derived) is not applicable 
    (argument mismatch; Base cannot be converted to Derived) 
method Consumer.set(Derived,Collection<? extends Consumer>) is not applicabl e 
    (argument mismatch; Base cannot be converted to Collection<? extends Consu mer>) 

PS: Danke an Holger, für Unvollständigkeit meiner Antwort darauf hingewiesen.

+2

Eigentlich ist der Java 8 Compiler nicht so schlau. In der Tat, neue Verbraucher(). get() 'kann ohne Probleme kompiliert werden.Und der Compilerfehler des Fragestellers sagt nichts darüber aus, wie falsch der ungeprüfte Vorgang ist, er sagt nur, dass der Methodenaufruf * mehrdeutig * ist. Das bedeutet, dass das Entfernen einer der beiden "set" -Methoden (unabhängig von der einen!) Den Compilerfehler "beheben" wird, selbst wenn das Ergebnis ist, dass die falsche Methode (erwartet eine 'Collection') aufgerufen wird. Oder, naja, * versucht *, da der Cast von 'Derived' nach' Collection' dann fehlschlägt. – Holger

+2

BTW. der Java 7 Compiler gibt keine Warnungen aus, es gibt * Fehler *. Nur die erste Zeile in Ihrer Ausgabe ist eine Warnung. – Holger

+0

@Holger stimme ich völlig zu. Danke für das Aufzeigen. Da war noch etwas anderes in meinem Kopf und ich versuchte, etwas anderes zu zeigen. Vielen Dank. Bearbeitete die Antwort. Danke noch einmal! – Jatin

Verwandte Themen