#java #sorting #lambda #java-8 #comparator
#java #сортировка #лямбда #java-8 #компаратор
Вопрос:
Я рассматривал разницу между Collections.sort
и list.sort
, в частности, в отношении использования Comparator
статических методов и того, требуются ли типы параметров в лямбда-выражениях. Прежде чем мы начнем, я знаю, что мог бы использовать ссылки на методы, например Song::getTitle
, для преодоления моих проблем, но мой запрос здесь — это не столько то, что я хочу исправить, сколько то, на что я хочу получить ответ, т. Е. Почему компилятор Java обрабатывает это таким образом.
Это моя находка. Предположим, у нас есть ArrayList
тип Song
of , с добавлением некоторых песен, есть 3 стандартных метода get:
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") );
Вот вызов обоих типов метода сортировки, который работает, без проблем:
Collections.sort(playlist1,
Comparator.comparing(p1 -> p1.getTitle()));
playlist1.sort(
Comparator.comparing(p1 -> p1.getTitle()));
Как только я начинаю цепочку thenComparing
, происходит следующее:
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())
);
т.е. синтаксические ошибки, потому что он больше не знает тип p1
. Поэтому, чтобы исправить это, я добавляю тип Song
к первому параметру (сравнения):
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())
);
Теперь начинается ЗАПУТАННАЯ часть. Для p laylist1.sort
, то есть Списка, это решает все ошибки компиляции для обоих следующих thenComparing
вызовов. Однако, для Collections.sort
, он решает его для первого, но не для последнего. Я протестировал добавленные несколько дополнительных вызовов thenComparing
, и он всегда показывает ошибку для последнего, если я не ввел (Song p1)
параметр.
Теперь я продолжил тестировать это дальше, создавая TreeSet
и используя 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())
);
Происходит то же самое, что и в, для TreeSet
, нет ошибок компиляции, но для Objects.compare
последнего вызова thenComparing
отображается ошибка.
Может кто-нибудь, пожалуйста, объяснить, почему это происходит, а также почему нет необходимости использовать (Song p1)
вообще при простом вызове метода сравнения (без дальнейших thenComparing
вызовов).
Еще один запрос по той же теме — когда я делаю это с TreeSet
:
Set<Song> set = new TreeSet<Song>(
Comparator.comparing(p1 -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist())
);
т.е. удалите тип Song
из первого лямбда-параметра для вызова метода сравнения, он показывает синтаксические ошибки при вызове comparing и первом вызове thenComparing
, но не при последнем вызове thenComparing
— почти противоположно тому, что происходило выше! Принимая во внимание, что для всех остальных 3 примеров, т.Е. С Objects.compare
, List.sort
и Collections.sort
когда я удаляю этот первый Song
тип параметра, он показывает синтаксические ошибки для всех вызовов.
Отредактировано, чтобы включить скриншот ошибок, которые я получал в Eclipse Kepler SR2, которые, как я теперь обнаружил, специфичны для Eclipse, потому что при компиляции с использованием компилятора Java JDK8 в командной строке он компилируется нормально.
Комментарии:
1. Было бы полезно, если бы вы включили в свой вопрос все сообщения об ошибках компиляции, которые вы получаете во всех своих тестах.
2. честно говоря, я думаю, что кому-то было бы проще всего увидеть, в чем проблемы, запустив исходный код самостоятельно.
3. Какие типы
t1
иt2
вObjects.compare
примере? Я пытаюсь вывести их, но наложение моего вывода типа на вывод типа компилятора является неразрешимым. 🙂4. Также какой компилятор вы используете?
5. Здесь возникают две отдельные проблемы. Один из ответчиков указал, что вы можете использовать ссылки на методы, которые вы вроде как отбросили. Так же, как лямбды представлены как в «явно типизированных», так и в «неявно типизированных» вариантах, ссылки на методы представлены в «точных» (одна перегрузка) и «неточных» (множественные перегрузки) вариантах. Для предоставления дополнительной информации о вводе, если она отсутствует, можно использовать либо точный метод ref, либо явный лямбда-выражение. (Также могут использоваться явные свидетели типов и приведения, но часто они являются более сложными.)
Ответ №1:
Во-первых, все примеры, которые, по вашим словам, вызывают ошибки, отлично компилируются с эталонной реализацией (javac из JDK 8.) Они также отлично работают в IntelliJ, поэтому вполне возможно, что ошибки, которые вы видите, специфичны для Eclipse.
Ваш основной вопрос, похоже, звучит так: «Почему он перестает работать, когда я начинаю цепочку». Причина в том, что, хотя лямбда-выражения и вызовы универсального метода являются поли-выражениями (их тип зависит от контекста), когда они отображаются как параметры метода, когда они появляются вместо этого как выражения получателя метода, они таковыми не являются.
Когда вы говорите
Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));
информации о типе достаточно для решения как comparing()
для аргумента типа, так и для типа аргумента p1
. comparing()
Вызов получает свой целевой тип из подписи Collections.sort
, поэтому известно comparing()
, что он должен возвращать a Comparator<Song>
и, следовательно p1
, должен быть Song
.
Но когда вы начинаете цепочку:
Collections.sort(playlist1,
comparing(p1 -> p1.getTitle())
.thenComparing(p1 -> p1.getDuration())
.thenComparing(p1 -> p1.getArtist()));
теперь у нас проблема. Мы знаем, что составное выражение comparing(...).thenComparing(...)
имеет целевой тип Comparator<Song>
, но поскольку выражение получателя для цепочки, comparing(p -> p.getTitle())
, является вызовом универсального метода, и мы не можем вывести его параметры типа из других его аргументов, нам отчасти не повезло. Поскольку мы не знаем тип этого выражения, мы не знаем, что у него есть thenComparing
метод и т. Д.
Есть несколько способов исправить это, все из которых включают ввод дополнительной информации о типе, чтобы можно было правильно ввести начальный объект в цепочке. Вот они, в грубом порядке уменьшения желательности и увеличения навязчивости:
- Используйте точную ссылку на метод (без перегрузок), например
Song::getTitle
. Затем это дает достаточно информации о типе, чтобы вывести переменные типа дляcomparing()
вызова и, следовательно, присвоить ему тип и, следовательно, продолжить цепочку. - Используйте явное лямбда-выражение (как вы делали в своем примере).
- Предоставьте свидетеля типа для
comparing()
вызова:Comparator.<Song, String>comparing(...)
. - Предоставьте явный целевой тип с приведением, приведя выражение получателя к
Comparator<Song>
.
Комментарии:
1. 1 за фактический ответ на ОП «почему компилятор не может вывести это», а не просто давать обходные пути / решения.
2. Спасибо за ваш ответ, Брайан. Тем не менее, я все еще нахожу что-то без ответа, почему List.sort ведет себя иначе, чем Collections.sort, поскольку для первого требуется только, чтобы первый лямбда-символ содержал тип параметра, но для последнего также требуется последний, например, если у меня есть сравнение, за которым следуют 5 вызовов thenComparing, я должен был бы поместить(Песня p1) в сравнении и в последнем сравнении. Также в моем исходном сообщении вы увидите нижний пример TreeSet, где я удаляю все типы параметров, и все же последний вызов thenComparing в порядке, а остальные — нет, так что это ведет себя по-другому.
3. @user3780370 Вы все еще используете компилятор Eclipse? Я не видел такого поведения, если я правильно понимаю ваш вопрос. Можете ли вы (а) попробовать это с помощью javac из JDK 8 и (б), если это все еще не удается, опубликовать код?
4. @BrianGoetz Спасибо за это предложение. Я только что скомпилировал его в окне командной строки, используя javac, и он компилируется, как вы сказали. Похоже, это проблема с Eclipse. Я еще не обновился до Eclipse Luna, который специально создан для JDK8, так что, надеюсь, это может быть исправлено. На самом деле у меня есть скриншот, чтобы показать вам, что происходило в Eclipse, но я не знаю, как опубликовать здесь.
5. Я думаю, вы имеете в виду
Comparator.<Song, String>comparing(...)
.
Ответ №2:
Проблема заключается в выводе типа. Без добавления a (Song s)
к первому сравнению comparator.comparing
не знает тип входных данных, поэтому по умолчанию используется Object .
Вы можете решить эту проблему 1 из 3 способов:
-
Используйте новый синтаксис ссылки на метод Java 8
Collections.sort(playlist, Comparator.comparing(Song::getTitle) .thenComparing(Song::getDuration) .thenComparing(Song::getArtist) );
-
Извлекайте каждый шаг сравнения в локальную ссылку
Comparator<Song> byName = (s1, s2) -> s1.getArtist().compareTo(s2.getArtist()); Comparator<Song> byDuration = (s1, s2) -> Integer.compare(s1.getDuration(), s2.getDuration()); Collections.sort(playlist, byName .thenComparing(byDuration) );
Редактировать
-
Принудительное использование типа, возвращаемого компаратором (обратите внимание, что вам нужны как тип ввода, так и тип ключа сравнения)
sort( Comparator.<Song, String>comparing((s) -> s.getTitle()) .thenComparing(p1 -> p1.getDuration()) .thenComparing(p1 -> p1.getArtist()) );
Я думаю thenComparing
, что синтаксическая ошибка «last» вводит вас в заблуждение. На самом деле это проблема типа со всей цепочкой, просто компилятор помечает конец цепочки как синтаксическую ошибку, потому что, я думаю, именно тогда конечный возвращаемый тип не совпадает.
Я не уверен, почему List
он выполняет лучшую работу по выводу, чем Collection
поскольку он должен выполнять тот же тип захвата, но, по-видимому, нет.
Комментарии:
1. Почему он знает это для
ArrayList
, но не дляCollections
решения (учитывая, что первый вызов в цепочке имеетSong
параметр)?2. Спасибо за ваш ответ, однако, если вы прочтете мой пост, вы увидите, что я сказал: «Прежде чем мы начнем, я знаю, что мог бы использовать ссылки на методы, например, Song:: getTitle, чтобы преодолеть мои проблемы, но мой запрос здесь — это не столько то, что я хочу исправить, сколько то, что я хочуответ на, т. Е. Почему компилятор Java обрабатывает его таким образом. »
3. Я хочу получить ответ на вопрос, почему компилятор ведет себя таким образом, когда я использую лямбда-выражения. Он принимает сравнение (s -> s.getArtist()), но затем, когда я, например, сопоставляю .thenComparing(s -> s.getDuration()), он выдает мне синтаксические ошибки для обоих вызовов, если я затем добавлю явный тип в сравнивающий вызов, например, сравнение (( Песня s) -> s.getArtist()) тогда это устраняет эту проблему, а для List.sort и TreeSet это также устраняет все дальнейшие ошибки компиляции без необходимости добавлять дополнительные типы параметров, однако, для коллекций. сортировка и объекты.сравнить примеры последнее сравнение по-прежнему завершается неудачей
Ответ №3:
Другой способ справиться с этой ошибкой во время компиляции:
Явно приведите переменную вашей первой функции сравнения, а затем все готово. Я отсортировал список org.bson.Объект Documents. Пожалуйста, посмотрите пример кода
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());
Ответ №4:
playlist1.sort(...)
создает границу песни для переменной типа E из объявления playlist1, которая «передается» в компаратор.
В Collections.sort(...)
, такой привязки нет, и вывода из типа первого компаратора недостаточно, чтобы компилятор мог вывести остальные.
Я думаю, вы получите «правильное» поведение Collections.<Song>sort(...)
, но у вас нет установки java 8, чтобы протестировать ее для вас.
Комментарии:
1. привет, да, вы правы в том, что добавляете коллекции. <Song> устраняет ошибку для последнего вызова thenComparing