Comparator.reversed () nie kompiluje się przy użyciu lambda

111

Mam listę z niektórymi obiektami użytkownika i próbuję posortować listę, ale działa tylko przy użyciu odwołania do metody, z wyrażeniem lambda kompilator wyświetla błąd:

List<User> userList = Arrays.asList(u1, u2, u3);
userList.sort(Comparator.comparing(u -> u.getName())); // works
userList.sort(Comparator.comparing(User::getName).reversed()); // works
userList.sort(Comparator.comparing(u -> u.getName()).reversed()); // Compiler error

Błąd:

com\java8\collectionapi\CollectionTest.java:35: error: cannot find symbol
            userList.sort(Comparator.comparing(u -> u.getName()).reversed());
                                                     ^
symbol:   method getName()
location: variable u of type Object
1 error
Andrey
źródło

Odpowiedzi:

145

Jest to słabość mechanizmu wnioskowania typu kompilatora. Aby wywnioskować typ ulambda, należy ustalić typ docelowy dla lambda. Jest to realizowane w następujący sposób. userList.sort()oczekuje argumentu typu Comparator<User>. W pierwszym wierszu Comparator.comparing()musi wrócić Comparator<User>. Oznacza to, że Comparator.comparing()potrzebuje Functionże bierze Userargument. Stąd w lambdzie w pierwszym wierszu umusi być typu Useri wszystko działa.

W drugim i trzecim wierszu pisanie docelowe jest zakłócane przez obecność wywołania reversed(). Nie jestem do końca pewien, dlaczego; zarówno odbiornik, jak i zwracany typ reversed()są, Comparator<T>więc wydaje się, że typ docelowy powinien być propagowany z powrotem do odbiorcy, ale tak nie jest. (Jak powiedziałem, to słabość.)

W drugim wierszu odwołanie do metody zawiera dodatkowe informacje o typie, które wypełniają tę lukę. Tych informacji nie ma w trzeciej linii, więc kompilator wnioskuje, uże jest Object(ostatnia deska ratunku), co kończy się niepowodzeniem.

Oczywiście, jeśli możesz użyć odwołania do metody, zrób to i zadziała. Czasami nie możesz użyć odwołania do metody, np. Jeśli chcesz przekazać dodatkowy parametr, więc musisz użyć wyrażenia lambda. W takim przypadku należałoby podać jawny typ parametru w parametrze lambda:

userList.sort(Comparator.comparing((User u) -> u.getName()).reversed());

Może być możliwe ulepszenie kompilatora, aby uwzględnić ten przypadek w przyszłej wersji.

Stuart Marks
źródło
28
Lambdy są podzielone na niejawnie wpisane (brak typów manifestu dla parametrów) i jawnie wpisane ; odwołania do metod są podzielone na dokładne (bez przeciążeń) i niedokładne . Gdy wywołanie metody ogólnej w pozycji odbiornika ma argumenty lambda, a parametrów typu nie można w pełni wywnioskować z innych argumentów, należy podać jawną lambdę, dokładną referencję metody, rzutowanie typu docelowego lub jawne świadków typu dla wywołanie metody ogólnej w celu podania dodatkowych informacji o typie potrzebnych do kontynuowania.
Brian Goetz
1
@StuartMarks, "nie jesteś do końca pewien, dlaczego" kompilator zachowuje się w ten sposób. Ale co mówi specyfikacja języka ? Czy powinno być wystarczających informacji do określenia typów ogólnych, zgodnie ze specyfikacją języka? Jeśli tak, jest to błąd kompilatora i powinien zostać zgłoszony i odpowiednio rozwiązany. W przeciwnym razie jest to dziedzina, w której należy poprawić język Java. Który to jest?
Garret Wilson
8
Myślę, że możemy potraktować komentarze Briana jako ostateczne, ponieważ napisał on specyfikację o której mowa :-)
minimalis
1
Niestety nic z tego nie wyjaśnia, dlaczego działa bez odwrócenia, podczas gdy nie działa z odwróceniem.
Chris311
90

Można obejść to ograniczenie za pomocą dwu- argumentu Comparator.comparingze Comparator.reverseOrder()jako drugi argument:

users.sort(comparing(User::getName, reverseOrder()));
Misha
źródło
4
Miły. Podoba mi się to bardziej niż używanie jawnie wpisanej lambdy. Albo jeszcze lepiej users.sort(reverseOrder(comparing(User::getName)));.
rolve
10
Zwróć uwagę, że reverseOrder(Comparator<T>)powyższa metoda jest włączona java.util.Collections, a nie Comparator.
rolve