Przyglądałem się różnicy między Collections.sort
i list.sort
, w szczególności w odniesieniu do używania Comparator
metod statycznych i tego, czy typy parametrów są wymagane w wyrażeniach lambda. Zanim zaczniemy, wiem, że mógłbym użyć referencji do metod, np. Song::getTitle
Aby przezwyciężyć swoje problemy, ale moje zapytanie tutaj nie jest czymś, co chcę naprawić, ale czymś, na co chcę odpowiedzi, tj. Dlaczego kompilator Java obsługuje to w ten sposób .
Oto moje odkrycie. Załóżmy, że mamy ArrayList
typ Song
, z dodanymi utworami, istnieją 3 standardowe metody pobierania:
ArrayList<Song> playlist1 = new ArrayList<Song>();
//add some new Song objects
playlist.addSong( new Song("Only Girl (In The World)", 235, "Rhianna") );
playlist.addSong( new Song("Thinking of Me", 206, "Olly Murs") );
playlist.addSong( new Song("Raise Your Glass", 202,"P!nk") );
Oto wywołanie obu typów metod sortowania, które działają, nie ma problemu:
Collections.sort(playlist1,
Comparator.comparing(p1 -> p1.getTitle()));
playlist1.sort(
Comparator.comparing(p1 -> p1.getTitle()));
Gdy tylko zacznę łączyć thenComparing
, dzieje się co następuje:
Collections.sort(playlist1,
Comparator.comparing(p1 -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
playlist1.sort(
Comparator.comparing(p1 -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
tj. błędy składniowe, ponieważ nie zna już typu p1
. Aby to naprawić, dodaję typ Song
do pierwszego parametru (porównania):
Collections.sort(playlist1,
Comparator.comparing((Song p1) -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
playlist1.sort(
Comparator.comparing((Song p1) -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
Teraz czas na część Mylące. Dla p laylist1.sort
, czyli Listy, rozwiązuje to wszystkie błędy kompilacji dla obu poniższych thenComparing
wywołań. Jednak Collections.sort
rozwiązuje to dla pierwszego, ale nie ostatniego. Testowałem dodałem kilka dodatkowych wywołań do thenComparing
i zawsze pokazuje błąd dla ostatniego, chyba że wstawię (Song p1)
parametr.
Teraz poszedłem dalej przetestować to, tworząc TreeSet
i używając Objects.compare
:
int x = Objects.compare(t1, t2,
Comparator.comparing((Song p1) -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
Set<Song> set = new TreeSet<Song>(
Comparator.comparing((Song p1) -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
To samo dzieje się w przypadku TreeSet
, nie ma błędów kompilacji, ale Objects.compare
ostatnie wywołanie thenComparing
pokazuje błąd.
Czy ktoś może wyjaśnić, dlaczego tak się dzieje, a także dlaczego w ogóle nie ma potrzeby używania, (Song p1)
gdy po prostu wywołuje metodę porównywania (bez dalszych thenComparing
wywołań).
Jeszcze jedno zapytanie na ten sam temat dotyczy tego TreeSet
:
Set<Song> set = new TreeSet<Song>(
Comparator.comparing(p1 -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
tzn. usuń typ Song
z pierwszego parametru lambda dla wywołania metody porównującej, pokaże błędy składniowe pod wywołaniem porównania i pierwsze wywołanie, thenComparing
ale nie ostatnie wywołanie thenComparing
- prawie odwrotność tego, co działo się powyżej! Podczas gdy dla wszystkich pozostałych 3 przykładów, tj. Z Objects.compare
, List.sort
i Collections.sort
kiedy usuwam ten pierwszy Song
typ parametru, pokazuje on błędy składniowe dla wszystkich wywołań.
Z góry bardzo dziękuję.
Edytowano tak, aby zawierał zrzut ekranu błędów, które otrzymałem w Eclipse Kepler SR2, które teraz znalazłem, są specyficzne dla Eclipse, ponieważ po skompilowaniu za pomocą kompilatora JDK8 java w wierszu poleceń kompiluje się OK.
t1
it2
naObjects.compare
przykład? Próbuję je wywnioskować, ale nakładanie mojego wnioskowania o typ na wnioskowanie o typie kompilatora jest trudne. :-)Odpowiedzi:
Po pierwsze, wszystkie podane przykłady powodują błędy kompilują się dobrze z implementacją referencyjną (javac z JDK 8.) Działają również dobrze w IntelliJ, więc jest całkiem możliwe, że błędy, które widzisz, są specyficzne dla Eclipse.
Wydaje się, że Twoje podstawowe pytanie brzmi: „dlaczego przestaje działać, kiedy zaczynam łączyć”. Przyczyną jest to, że chociaż wyrażenia lambda i wywołania metod ogólnych są wyrażeniami poli (ich typ jest zależny od kontekstu), gdy pojawiają się jako parametry metody, to gdy pojawiają się jako wyrażenia odbiorcze metody, tak nie jest.
Kiedy powiesz
jest wystarczająco dużo informacji o typie do rozwiązania zarówno dla argumentu typu, jak
comparing()
i dla typu argumentup1
.comparing()
Rozmowa staje się jego typ docelowy z podpisemCollections.sort
, więc wiadomo,comparing()
musi zwrócićComparator<Song>
, a zatemp1
musi byćSong
.Ale kiedy zaczniesz łączyć:
teraz mamy problem. Wiemy, że wyrażenie złożone
comparing(...).thenComparing(...)
ma typ docelowyComparator<Song>
, ale ponieważ wyrażenie odbierające dla łańcuchacomparing(p -> p.getTitle())
jest ogólnym wywołaniem metody i nie możemy wywnioskować jego parametrów typu z innych argumentów, nie mamy szczęścia . Ponieważ nie znamy typu tego wyrażenia, nie wiemy, czy ma onothenComparing
metodę itp.Istnieje kilka sposobów rozwiązania tego problemu, z których wszystkie obejmują wstrzyknięcie większej ilości informacji o typie, aby można było poprawnie wpisać początkowy obiekt w łańcuchu. Oto one, w przybliżonej kolejności malejącej atrakcyjności i rosnącej inwazyjności:
Song::getTitle
. To daje wystarczającą ilość informacji o typie, aby wywnioskować zmienne typu dlacomparing()
wywołania, a zatem nadać mu typ, a zatem kontynuować w dół łańcucha.comparing()
rozmowy:Comparator.<Song, String>comparing(...)
.Comparator<Song>
.źródło
Comparator.<Song, String>comparing(...)
.Problemem jest wnioskowanie typu. Bez dodania
(Song s)
do pierwszego porównaniacomparator.comparing
nie zna typu danych wejściowych, więc domyślnie przyjmuje wartość Object.Możesz rozwiązać ten problem na 1 z 3 sposobów:
Użyj nowej składni odwołania do metody Java 8
Wyciągnij każdy krok porównania do lokalnego odniesienia
EDYTOWAĆ
Wymuszanie typu zwracanego przez komparator (pamiętaj, że potrzebujesz zarówno typu danych wejściowych, jak i typu klucza porównania)
Myślę, że „ostatni”
thenComparing
błąd składni wprowadza Cię w błąd. W rzeczywistości jest to problem z typem w całym łańcuchu, po prostu kompilator zaznacza tylko koniec łańcucha jako błąd składniowy, ponieważ myślę, że wtedy ostateczny typ zwracany nie pasuje.Nie jestem pewien, dlaczego
List
wykonuje lepsze zadanie wnioskowania niżCollection
skoro powinien wykonywać ten sam typ przechwytywania, ale najwyraźniej nie.źródło
ArrayList
ale nie dlaCollections
rozwiązania (biorąc pod uwagę, że pierwsze wywołanie w łańcuchu maSong
parametr)?Inny sposób radzenia sobie z tym błędem czasu kompilacji:
Prześlij jawnie zmienną swojej pierwszej funkcji porównującej, a następnie gotowe. Posortowałem listę obiektu org.bson.Documents. Proszę spojrzeć na przykładowy kod
Comparator<Document> comparator = Comparator.comparing((Document hist) -> (String) hist.get("orderLineStatus"), reverseOrder()) .thenComparing(hist -> (Date) hist.get("promisedShipDate")) .thenComparing(hist -> (Date) hist.get("lastShipDate")); list = list.stream().sorted(comparator).collect(Collectors.toList());
źródło
playlist1.sort(...)
tworzy granicę Song dla zmiennej typu E, z deklaracji playlist1, która „faluje” do komparatora.W programie
Collections.sort(...)
nie ma takiego ograniczenia, a wnioskowanie z typu pierwszego komparatora nie jest wystarczające, aby kompilator wywnioskował resztę.Myślę, że uzyskałbyś „poprawne” zachowanie
Collections.<Song>sort(...)
, ale nie masz instalacji java 8, aby to przetestować.źródło