Wyrażenie lambda i metoda generyczna

111

Załóżmy, że mam ogólny interfejs:

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

I metoda sort:

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

Mogę wywołać tę metodę i przekazać jako argument wyrażenie lambda:

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

To zadziała dobrze.

Ale teraz, jeśli sprawię, że interfejs nie będzie generyczny, a metoda generyczna:

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) {
}

A następnie wywołaj to w ten sposób:

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

Nie kompiluje się. Pokazuje błąd w wyrażeniu lambda mówiącym:

„Metoda docelowa jest ogólna”

OK, kiedy skompilowałem go za pomocą javac, pokazuje następujący błąd:

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

Na podstawie tego komunikatu o błędzie wydaje się, że kompilator nie jest w stanie wywnioskować argumentów typu. Czy tak jest? Jeśli tak, to dlaczego tak się dzieje?

Próbowałem różnych sposobów, szukałem w Internecie. Potem znalazłem ten artykuł JavaCodeGeeks , który pokazuje sposób, więc spróbowałem:

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

co znowu nie działa, w przeciwieństwie do tego, co twierdzi ten artykuł, że działa. Możliwe, że działał w niektórych początkowych wersjach.

Moje pytanie brzmi: czy istnieje sposób na utworzenie wyrażenia lambda dla metody ogólnej? Mogę to jednak zrobić za pomocą odwołania do metody, tworząc metodę:

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

w jakiejś klasie powiedz SOi przekaż to jako:

sort(list, SO::compare);
Rohit Jain
źródło

Odpowiedzi:

117

Nie można użyć wyrażenia lambda dla interfejsu funkcjonalnego , jeśli metoda w interfejsie funkcjonalnym ma parametry typu . Zobacz sekcję §15.27.3 w JLS8 :

Wyrażenie lambda jest zgodne [..] z typem docelowym T, jeśli T jest typem interfejsu funkcjonalnego (§9.8), a wyrażenie jest zgodne z typem funkcji [..] T. [..] Wyrażenie lambda jest przystające z typem funkcji, jeśli spełnione są wszystkie poniższe warunki:

  • Typ funkcji nie ma parametrów typu .
  • [..]
nosid
źródło
47
Jednak to ograniczenie nie ma zastosowania do odwołań do metod do metod ogólnych. Można użyć odwołania do metody do metody ogólnej z ogólnym interfejsem funkcjonalnym.
Brian Goetz
17
Jestem pewien, że istnieje dobry powód dla tego ograniczenia. Co to jest?
Sandro
6
@Sandro: po prostu nie ma składni do deklarowania parametrów typu dla wyrażenia lambda. A taka składnia byłaby bardzo skomplikowana. Należy pamiętać, że parser nadal musi być w stanie powiedzieć takie wyrażenie lambda z parametrami typu, niezależnie od innych legalnych konstrukcji Java. Dlatego musisz uciekać się do odwołań do metod. Metoda docelowa może deklarować parametry typu przy użyciu ustalonej składni.
Holger,
2
@Holger nadal, gdzie parametry typu mogą być wywnioskowane automatycznie, kompilator może oszukać typ przechwytywania, tak jak robi to, gdy deklarujesz np. Set <?> I sprawdzasz typ z tymi przechwyconymi typami. Oczywiście, to uniemożliwia podanie ich jako parametrów typu w treści, ale jeśli potrzebujesz tego, uciekanie się do odwołań do metod jest dobrą alternatywą
WorldSEnder
17

Korzystając z odwołania do metody, znalazłem inny sposób przekazania argumentu:

List<String> list = Arrays.asList("a", "b", "c");        
sort(list, Comparable::<String>compareTo);
Andrey
źródło
3

Po prostu wskaż kompilatorowi odpowiednią wersję ogólnego Komparatora z (Comparator<String>)

Więc odpowiedź będzie

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

Aleч
źródło
2
incompatible types: java.util.Comparator<java.lang.String> cannot be converted to MyComparablei MyComparablenie jest ogólny (brak typu), więc też (MyComparable<String>)nie zadziała
user85421
1
Nie wiem, jak wpisujesz kod @CarlosHeuberger, ale u mnie działa bardzo dobrze, właśnie tego szukałem.
Ivan Perales M.
@IvanPeralesM. po 2 miesiącach ... skopiowałem i wkleiłem Twój kod oraz skopiowałem i wkleiłem powyższy wiersz nad linią sortowania - po prostu tak, jak tutaj: ideone.com/YNwBbF ! Czy na pewno dokładnie wpisałeś powyższy kod? używając Compartor?
user85421
Nie, nie, używam idei stojącej za odpowiedzią, aby rzucić funkcjonalność, aby powiedzieć kompilacji, jaki to jest typ i zadziałała.
Ivan Perales M.
@IvanPeralesM. więc jaki jest twój problem z moim „pisaniem”? Odpowiedź, tak jak jest opublikowana, nie działa.
user85421
0

Masz na myśli coś takiego ?:

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

Jakiego typu jest ta lambda? Nie można tego wyrazić w Javie i dlatego nie można skomponować tego wyrażenia w aplikacji funkcji, a wyrażenia muszą być składowalne.

Do tego potrzeba by było wsparcie dla typów Rank2 w Javie.

Metody mogą być ogólne, ale w związku z tym nie można ich używać jako wyrażeń. Można je jednak zredukować do wyrażenia lambda, specjalizując wszystkie niezbędne typy ogólne, zanim będzie można je przekazać:ClassName::<TypeName>methodName

iconfly
źródło
1
"Of what type is this lambda? You couldn't express that in Java..."Typ zostałby wywnioskowany przy użyciu kontekstu, tak jak w przypadku każdej innej lambdy. Typ lambda nie jest jawnie wyrażony w samej lambdzie.
Kröw