Dieses Programm wird in Java 7 (oder in Java 8 mit -source 7
) problemlos kompiliert, kann jedoch nicht mit Java 8 kompiliert werden:
interface Iface<T> {}
class Impl implements Iface<Impl> {}
class Acceptor<T extends Iface<T>> {
public Acceptor(T obj) {}
}
public class Main {
public static void main(String[] args) {
Acceptor<?> acceptor = new Acceptor<>(new Impl());
}
}
Ergebnis:
Main.java:10: error: incompatible types: cannot infer type arguments for Acceptor<>
Acceptor<?> acceptor = new Acceptor<>(new Impl());
^
reason: inference variable T has incompatible bounds
equality constraints: Impl
upper bounds: Iface<CAP#1>,Iface<T>
where T is a type-variable:
T extends Iface<T> declared in class Acceptor
where CAP#1 is a fresh type-variable:
CAP#1 extends Iface<CAP#1> from capture of ?
1 error
Mit anderen Worten, dies ist eine Inkompatibilität der Rückwärtsquelle zwischen Java 7 und 8. Ich habe die Liste der Inkompatibilitäten zwischen Java SE 8 und Java SE 7 durchgearbeitet, aber nichts gefunden, was zu meinem Problem passen würde.
Also, ist das ein Fehler?
Umgebung:
$ /usr/lib/jvm/java-8-oracle/bin/java -version
java version "1.8.0"
Java(TM) SE Runtime Environment (build 1.8.0-b132)
Java HotSpot(TM) 64-Bit Server VM (build 25.0-b70, mixed mode)
Acceptor<?> acceptor = new Acceptor<Impl>(new Impl())
sollte gut funktionieren oderAcceptor<Impl> acceptor = new Acceptor<>(new Impl())
.Antworten:
Danke für den Bericht. Das sieht aus wie ein Fehler. Ich werde mich darum kümmern und wahrscheinlich eine bessere Antwort hinzufügen, sobald wir mehr Informationen darüber haben, warum dies geschieht. Ich habe diesen Fehlereintrag JDK-8043926 abgelegt , um ihn zu verfolgen.
quelle
Die Java-Sprachspezifikation hat sich hinsichtlich der Typinferenz erheblich geändert . In JLS7 wurde die Typinferenz in §15.12.2.7 und §15.12.2.8 beschrieben , während in JLS8 ein ganzes Kapitel Kapitel 18 gewidmet ist. Typinferenz .
Die Regeln sind sowohl in JLS7 als auch in JLS8 recht komplex. Es ist schwierig, die Unterschiede zu erkennen, aber es gibt offensichtlich Unterschiede, wie aus Abschnitt 18.5.2 hervorgeht :
Die Absicht der Änderung war jedoch, abwärtskompatibel zu sein. Siehe den letzten Absatz von Abschnitt §18.5.2 :
Ich kann nicht sagen, ob das stimmt oder nicht. Es gibt jedoch einige interessante Variationen Ihres Codes, die das Problem nicht zeigen. Die folgende Anweisung wird beispielsweise fehlerfrei kompiliert:
new Acceptor<>(new Impl());
In diesem Fall gibt es keinen Zieltyp . Das bedeutet, dass der Ausdruck zum Erstellen einer Klasseninstanz kein Polyausdruck ist und die Regeln für die Typinferenz einfacher sind. Siehe §18.5.2 :
Das ist auch der Grund, warum die folgende Aussage funktioniert.
Acceptor<?> acceptor = (Acceptor<?>) new Acceptor<>(new Impl());
Obwohl es im Kontext des Ausdrucks einen Typ gibt, zählt dieser nicht als Zieltyp . Wenn ein Ausdruck zur Erstellung einer Klasseninstanz weder in einem Zuweisungsausdruck noch in einem Aufrufausdruck vorkommt , kann er kein Polyausdruck sein . Siehe §15.9 :
Zurück zu Ihrer Aussage. Der relevante Teil des JLS8 ist wieder §18.5.2 . Ich kann Ihnen jedoch nicht sagen, ob die folgende Aussage gemäß JLS8 korrekt ist oder ob der Compiler mit der Fehlermeldung Recht hat. Aber zumindest haben Sie einige Alternativen und Hinweise für weitere Informationen.
Acceptor<?> acceptor = new Acceptor<>(new Impl());
quelle
new Acceptor<?>(...)
dies in beiden Versionen nicht zulässig ist. Wenn überhaupt, ist es ein Fehler in der Typinferenz von Java7new Acceptor<Impl>(...)
. AFAIK, Java 7-Typinferenz berücksichtigt niemals den erwarteten Ergebnistyp.List<Integer> list = new ArrayList<>();
Dies müsste der Fall sein, da die vorherige Anweisung nicht genügend Informationen enthält, um eine Schlussfolgerung zu ziehen.Die Typinferenz wurde in Java 8 geändert. Die Typinferenz untersucht nun sowohl den Zieltyp als auch die Parametertypen für Konstruktoren und Methoden. Folgendes berücksichtigen:
interface Iface {} class Impl implements Iface {} class Impl2 extends Impl {} class Acceptor<T> { public Acceptor(T obj) {} } <T> T foo(T a) { return a; }
Folgendes ist jetzt in Java 8 in Ordnung (aber nicht in Java 7):
Acceptor<Impl> a = new Acceptor<>(new Impl2()); // Java 8 cleverly infers Acceptor<Impl> // While Java 7 infers Acceptor<Impl2> (causing an error)
Dies führt natürlich in beiden Fällen zu einem Fehler:
Acceptor<Impl> a = new Acceptor<Impl2>(new Impl2());
Dies ist auch in Java 8 in Ordnung:
Acceptor<Impl> a = foo (new Acceptor<>(new Impl2())); // Java 8 infers Acceptor<Impl> even in this case // While Java 7, again, infers Acceptor<Impl2> // and gives: incompatible types: Acceptor<Impl2> cannot be converted to Acceptor<Impl>
Das Folgende gibt in beiden Fällen einen Fehler aus, der jedoch unterschiedlich ist:
Acceptor<Impl> a = foo (new Acceptor<Impl2>(new Impl2())); // Java 7: // incompatible types: Acceptor<Impl2> cannot be converted to Acceptor<Impl> // Java 8: // incompatible types: inferred type does not conform to upper bound(s) // inferred: Acceptor<Impl2> // upper bound(s): Acceptor<Impl>,java.lang.Object
Java 8 hat das Typinferenzsystem eindeutig intelligenter gemacht. Verursacht dies Inkompatibilitäten? Im Allgemeinen nein. Aufgrund der Typlöschung spielt es keine Rolle, welche Typen abgeleitet wurden, solange das Programm kompiliert wird. Kompiliert Java 8 alle Java 7-Programme? Es sollte, aber Sie haben einen Fall angesprochen, in dem dies nicht der Fall ist.
Was zu passieren scheint, ist, dass Java 8 Wildcards nicht gut verarbeitet. Anstatt sie als Fehlen einer Einschränkung zu betrachten, scheint es, sie als restriktive Einschränkung zu behandeln, die sie nicht erfüllen können. Ich bin mir nicht sicher, ob es dem Brief der JLS folgt, aber ich würde dies zumindest im Geiste einen Fehler nennen.
Zu
Acceptor
Ihrer Information, dies funktioniert (beachten Sie, dass meine nicht die Typbeschränkungen hat, die Ihre haben):Acceptor<?> a = new Acceptor<>(new Impl2());
Beachten Sie, dass in Ihrem Beispiel ein Platzhaltertyp außerhalb eines Methodenparameters verwendet wird (was nicht ratsam ist). Ich frage mich, ob das gleiche Problem in typischerem Code auftritt, der den Diamantoperator in Methodenaufrufen verwendet. (Wahrscheinlich.)
quelle
new Foo<>(..)
berücksichtigt definitiv die Konstruktorargumente für die Inferenz.