Ich beantwortete eine Frage und stieß auf ein Szenario, das ich nicht erklären kann. Betrachten Sie diesen Code:
interface ConsumerOne<T> {
void accept(T a);
}
interface CustomIterable<T> extends Iterable<T> {
void forEach(ConsumerOne<? super T> c); //overload
}
class A {
private static CustomIterable<A> iterable;
private static List<A> aList;
public static void main(String[] args) {
iterable.forEach(a -> aList.add(a)); //ambiguous
iterable.forEach(aList::add); //ambiguous
iterable.forEach((A a) -> aList.add(a)); //OK
}
}
Ich verstehe nicht, warum das explizite Eingeben des Parameters des Lambda (A a) -> aList.add(a)
den Code kompilieren lässt. Warum ist es außerdem Iterable
eher mit der Überlastung als mit der Überlastung verbunden CustomIterable
?
Gibt es eine Erklärung dafür oder einen Link zu dem relevanten Abschnitt der Spezifikation?
Hinweis: iterable.forEach((A a) -> aList.add(a));
Kompiliert nur, wenn es CustomIterable<T>
erweitert wird Iterable<T>
(eine flache Überladung der Methoden CustomIterable
führt zu einem mehrdeutigen Fehler).
Bekomme das auf beiden:
- openjdk Version "13.0.2" 2020-01-14
Eclipse Compiler - openjdk version "1.8.0_232"
Eclipse-Compiler
Bearbeiten : Der obige Code kann beim Erstellen mit maven nicht kompiliert werden, während Eclipse die letzte Codezeile erfolgreich kompiliert.
Antworten:
TL; DR, dies ist ein Compiler-Fehler.
Es gibt keine Regel, die einer bestimmten anwendbaren Methode Vorrang einräumt, wenn sie vererbt wird, oder einer Standardmethode. Interessanterweise, wenn ich den Code auf ändere
Die
iterable.forEach((A a) -> aList.add(a));
Anweisung erzeugt einen Fehler in Eclipse.Da sich beim Deklarieren einer weiteren Überladung keine Eigenschaft der
forEach(Consumer<? super T) c)
Methode über dieIterable<T>
Schnittstelle geändert hat, kann die Entscheidung von Eclipse, diese Methode auszuwählen, nicht (konsistent) auf einer Eigenschaft der Methode basieren. Es ist immer noch die einzige geerbte Methode, immer noch die einzigedefault
Methode, immer noch die einzige JDK-Methode und so weiter. Keine dieser Eigenschaften sollte sich ohnehin auf die Methodenauswahl auswirken.Beachten Sie, dass Sie die Deklaration in ändern
erzeugt auch einen "mehrdeutigen" Fehler, so dass die Anzahl der anwendbaren überladenen Methoden ebenfalls keine Rolle spielt, selbst wenn es nur zwei Kandidaten gibt, gibt es keine allgemeine Präferenz für
default
Methoden.Bisher scheint das Problem aufzutreten, wenn zwei anwendbare Methoden vorhanden sind und eine
default
Methode und eine Vererbungsbeziehung beteiligt sind, aber dies ist nicht der richtige Ort, um weiter zu graben.Es ist jedoch verständlich, dass die Konstrukte Ihres Beispiels möglicherweise von unterschiedlichem Implementierungscode im Compiler verarbeitet werden, wobei einer einen Fehler aufweist, der andere jedoch nicht.
a -> aList.add(a)
ist ein implizit typisierter Lambda-Ausdruck, der nicht für die Überlastungsauflösung verwendet werden kann. Im Gegensatz dazu(A a) -> aList.add(a)
handelt es sich um einen explizit typisierten Lambda-Ausdruck, mit dem eine übereinstimmende Methode aus den überladenen Methoden ausgewählt werden kann. Dies hilft hier jedoch nicht (sollte hier nicht helfen), da alle Methoden Parametertypen mit genau derselben funktionalen Signatur haben .Als Gegenbeispiel mit
Die funktionalen Signaturen unterscheiden sich, und die Verwendung eines explizit eingegebenen Lambda-Ausdrucks kann in der Tat bei der Auswahl der richtigen Methode hilfreich sein, während der implizit typisierte Lambda-Ausdruck nicht hilfreich ist und daher
forEach(s -> s.isEmpty())
einen Compilerfehler erzeugt. Und alle Java-Compiler sind sich einig.Beachten Sie, dass dies
aList::add
eine mehrdeutige Methodenreferenz ist, da dieadd
Methode ebenfalls überladen ist, sodass sie auch bei der Auswahl einer Methode helfen kann. Methodenreferenzen werden jedoch möglicherweise trotzdem von einem anderen Code verarbeitet. Das Wechseln zu einer eindeutigenaList::contains
oder das ÄndernList
zuCollection
, umadd
eindeutig zu machen , hat das Ergebnis in meiner Eclipse-Installation (die ich verwendet habe2019-06
) nicht geändert .quelle
default
Methode ist, ist nur ein zusätzlicher Punkt. Meine Antwort zeigt bereits ein Beispiel, in dem Eclipse der Standardmethode keinen Vorrang einräumt.default
das Ergebnis ändert, und sofort davon ausgegangen, dass der Grund für das beobachtete Verhalten gefunden wurde. Sie sind so übermütig, dass Sie andere Antworten als falsch bezeichnen, obwohl sie nicht einmal widersprüchlich sind. Weil Sie Ihr eigenes Verhalten über andere projizieren, habe ich nie behauptet, dass Vererbung der Grund sei . Ich habe bewiesen, dass es nicht so ist. Ich habe gezeigt, dass das Verhalten inkonsistent ist, da Eclipse die bestimmte Methode in einem Szenario auswählt, jedoch nicht in einem anderen, in dem drei Überladungen vorhanden sind.default
andereabstract
, mit zwei verbraucherähnlichen Argumenten erstellt und ausprobiert. Eclipse sagt zu Recht, dass es mehrdeutig ist, obwohl es sich um einedefault
Methode handelt. Anscheinend ist die Vererbung für diesen Eclipse-Fehler immer noch relevant, aber im Gegensatz zu Ihnen werde ich nicht verrückt und nenne andere Antworten falsch, nur weil sie den Fehler nicht vollständig analysiert haben. Das ist hier nicht unsere Aufgabe.Der Eclipse-Compiler wird korrekt in die
default
Methode aufgelöst , da dies die spezifischste Methode gemäß der Java-Sprachspezifikation 15.12.2.5 ist :javac
(wird standardmäßig von Maven und IntelliJ verwendet) teilt mit, dass der Methodenaufruf hier nicht eindeutig ist. Laut Java Language Specification ist dies jedoch nicht mehrdeutig, da eine der beiden Methoden hier die spezifischste ist.Implizit typisierte Lambda-Ausdrücke werden anders behandelt als explizit typisierte Lambda-Ausdrücke in Java. Implizit typisiert im Gegensatz zu explizit typisierten Lambda-Ausdrücken durchlaufen die erste Phase, um die Methoden des strengen Aufrufs zu identifizieren (siehe Java-Sprachspezifikation jls-15.12.2.2 , erster Punkt). Daher ist der Methodenaufruf hier für implizit typisierte Lambda-Ausdrücke nicht eindeutig .
In Ihrem Fall besteht die Problemumgehung für diesen
javac
Fehler darin, den Typ der Funktionsschnittstelle anzugeben , anstatt einen explizit typisierten Lambda-Ausdruck wie folgt zu verwenden:oder
Hier ist Ihr Beispiel zum Testen weiter minimiert:
quelle
default
Methode beendet, wenn drei Kandidatenmethoden vorhanden sind oder wenn beide Methoden in derselben Schnittstelle deklariert sind.forEach(Consumer)
undforEach(Consumer2)
die niemals zur gleichen Implementierungsmethode führen können.Der Code, in dem Eclipse JLS §15.12.2.5 implementiert, findet keine der beiden Methoden spezifischer als die andere, selbst für den Fall des explizit typisierten Lambda.
Im Idealfall würde Eclipse hier anhalten und Unklarheiten melden. Leider enthält die Implementierung der Überlastungsauflösung zusätzlich zur Implementierung von JLS nicht trivialen Code. Nach meinem Verständnis muss dieser Code (der aus der Zeit stammt, als Java 5 neu war) beibehalten werden, um einige Lücken in JLS zu schließen.
Ich habe eingereicht https://bugs.eclipse.org/562538 , um dies zu verfolgen.
Unabhängig von diesem speziellen Fehler kann ich nur dringend von diesem Codestil abraten. Überladung ist gut für eine gute Anzahl von Überraschungen in Java, multipliziert mit Lambda-Typ-Inferenz. Die Komplexität ist im Verhältnis zum wahrgenommenen Gewinn ziemlich unverhältnismäßig.
quelle