Lambda-Ausdruck und generische Methode

111

Angenommen, ich habe eine generische Schnittstelle:

interface MyComparable<T extends Comparable<T>>  {
    public int compare(T obj1, T obj2);
}

Und eine Methode sort:

public static <T extends Comparable<T>> 
       void sort(List<T> list, MyComparable<T> comp) {
    // sort the list
}

Ich kann diese Methode aufrufen und einen Lambda-Ausdruck als Argument übergeben:

List<String> list = Arrays.asList("a", "b", "c");
sort(list, (a, b) -> a.compareTo(b));

Das wird gut funktionieren.

Aber jetzt, wenn ich die Schnittstelle nicht generisch und die Methode generisch mache:

interface MyComparable {
    public <T extends Comparable<T>> int compare(T obj1, T obj2);
}

public static <T extends Comparable<T>> 
       void sort(List<T> list, MyComparable comp) {
}

Und rufen Sie dann Folgendes auf:

List<String> list = Arrays.asList("a", "b", "c");
sort(list, (a, b) -> a.compareTo(b));

Es wird nicht kompiliert. Es zeigt einen Fehler beim Lambda-Ausdruck, der sagt:

"Zielmethode ist generisch"

OK, wenn ich es mit kompiliert habe javac, wird folgender Fehler angezeigt:

SO.java:20: error: incompatible types: cannot infer type-variable(s) T#1
        sort(list, (a, b) -> a.compareTo(b));
            ^
    (argument mismatch; invalid functional descriptor for lambda expression
      method <T#2>(T#2,T#2)int in interface MyComparable is generic)
  where T#1,T#2 are type-variables:
    T#1 extends Comparable<T#1> declared in method <T#1>sort(List<T#1>,MyComparable)
    T#2 extends Comparable<T#2> declared in method <T#2>compare(T#2,T#2)
1 error

Aus dieser Fehlermeldung geht hervor, dass der Compiler die Typargumente nicht ableiten kann. Ist das der Fall? Wenn ja, warum passiert das dann so?

Ich habe verschiedene Möglichkeiten ausprobiert und über das Internet gesucht. Dann habe ich diesen JavaCodeGeeks-Artikel gefunden , der einen Weg zeigt, also habe ich versucht:

sort(list, <T extends Comparable<T>>(a, b) -> a.compareTo(b));

was wiederum nicht funktioniert, im Gegensatz zu dem, was dieser Artikel behauptet, dass es funktioniert. Möglicherweise funktionierte es in einigen ersten Builds.

Meine Frage lautet also: Gibt es eine Möglichkeit, einen Lambda-Ausdruck für eine generische Methode zu erstellen? Ich kann dies jedoch mithilfe einer Methodenreferenz tun, indem ich eine Methode erstelle:

public static <T extends Comparable<T>> int compare(T obj1, T obj2) {
    return obj1.compareTo(obj2);
}

in einer Klasse sagen SOund weitergeben als:

sort(list, SO::compare);
Rohit Jain
quelle

Antworten:

117

Sie können keinen verwenden Lambda - Ausdruck für eine funktionale Schnittstelle , wenn das Verfahren in der Funktionsschnittstelle hat Typparameter . Siehe Abschnitt §15.27.3 in JLS8 :

Ein Lambda - Ausdruck kompatibel ist [..] mit einer Ziel - Typ T , wenn T ein funktioneller Schnittstellentyp (§9.8) ist und der Ausdruck ist deckungsgleich mit dem Funktionstyp [..] T. [..] Ein Lambda - Ausdruck ist kongruent mit einem Funktionstyp, wenn alle der folgenden Bedingungen erfüllt sind:

  • Der Funktionstyp hat keine Typparameter .
  • [..]
nosid
quelle
47
Diese Einschränkung gilt jedoch nicht für Methodenverweise auf generische Methoden. Sie können einen Methodenverweis auf eine generische Methode mit einer generischen Funktionsschnittstelle verwenden.
Brian Goetz
17
Ich bin sicher, es gibt einen guten Grund für diese Einschränkung. Was ist es?
Sandro
6
@ Sandro: Es gibt einfach keine Syntax zum Deklarieren von Typparametern für einen Lambda-Ausdruck. Und eine solche Syntax wäre sehr kompliziert. Beachten Sie, dass der Parser weiterhin in der Lage sein muss, einen solchen Lambda-Ausdruck mit Typparametern von anderen legalen Java-Konstrukten zu unterscheiden. Daher müssen Sie auf Methodenreferenzen zurückgreifen. Die Zielmethode kann Typparameter mithilfe einer festgelegten Syntax deklarieren.
Holger
2
@Holger noch, wo Typparameter automatisch abgeleitet werden können, könnte der Compiler einen Capure-Typ wie bei der Deklaration von zB Set <?> Entscheiden und Typprüfungen mit diesen erfassten Typen durchführen. Sicher, das macht es unmöglich, sie als Typparameter im Körper
anzugeben
17

Unter Verwendung der Methodenreferenz habe ich einen anderen Weg gefunden, um das Argument zu übergeben:

List<String> list = Arrays.asList("a", "b", "c");        
sort(list, Comparable::<String>compareTo);
Andrey
quelle
3

Zeigen Sie dem Compiler einfach die richtige Version des generischen Komparators mit (Comparator<String>)

Die Antwort wird also sein

sort(list, (Comparator<String>)(a, b) -> a.compareTo(b));

Aleч
quelle
2
incompatible types: java.util.Comparator<java.lang.String> cannot be converted to MyComparableund MyComparableist nicht generisch (kein Typ), (MyComparable<String>)würde also auch nicht funktionieren
user85421
1
Ich weiß nicht, wie Sie den Code @CarlosHeuberger eingeben, aber es funktioniert sehr gut für mich, das ist, wonach ich gesucht habe.
Ivan Perales M.
@IvanPeralesM. nach 2 Monaten ... Ich habe Ihren Code kopiert und eingefügt und über der Zeile über Ihre Sortierzeile kopiert und eingefügt - genau das, wie hier: ideone.com/YNwBbF ! Sind Sie sicher, dass Sie den obigen Code genau eingegeben haben? mit Compartor?
Benutzer85421
Nein, ich nicht, ich benutze die Idee hinter der Antwort, um die Funktion zu besetzen, um der Kompilierung mitzuteilen, um welchen Typ es sich handelt und es funktioniert hat.
Ivan Perales M.
@IvanPeralesM. Nun, was ist dein Problem mit meinem "Tippen"? Die Antwort, wie sie veröffentlicht wird, funktioniert nicht.
Benutzer85421
0

Du meinst so etwas?:

<T,S>(T t, S s)->...

Von welcher Art ist dieses Lambda? Sie konnten dies in Java nicht ausdrücken und können diesen Ausdruck daher nicht in einer Funktionsanwendung zusammenstellen, und Ausdrücke müssen zusammensetzbar sein.

Damit dies funktioniert, benötigen Sie Unterstützung für Rank2-Typen in Java.

Methoden dürfen generisch sein, daher können Sie sie nicht als Ausdrücke verwenden. Sie können jedoch auf den Lambda-Ausdruck reduziert werden, indem Sie alle erforderlichen generischen Typen spezialisieren, bevor Sie sie übergeben können:ClassName::<TypeName>methodName

iconfly
quelle
1
"Of what type is this lambda? You couldn't express that in Java..."Der Typ würde unter Verwendung des Kontexts abgeleitet, genau wie bei jedem anderen Lambda. Der Typ eines Lambda wird im Lambda selbst nicht explizit ausgedrückt.
Kröw