Я изучал разницу между Collections.sort
и list.sort
, особенно в отношении использования Comparator
статических методов и того, требуются ли типы параметров в лямбда-выражениях. Прежде чем мы начнем, я знаю, что могу использовать ссылки на методы, например, Song::getTitle
для решения моих проблем, но мой запрос здесь - это не столько то, что я хочу исправить, а то, на что я хочу получить ответ, т.е. почему компилятор Java обрабатывает это таким образом .
Это моя находка. Предположим, у нас есть ArrayList
тип Song
, с добавленными песнями, есть 3 стандартных метода получения:
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
из первого лямбда-параметра для вызова метода сравнения, он показывает синтаксические ошибки при вызове сравнения и первом вызове, thenComparing
но не последнем вызове thenComparing
- почти полная противоположность тому, что происходило выше! Принимая во внимание, что для всех остальных трех примеров, то есть с Objects.compare
, List.sort
и Collections.sort
когда я удаляю это первымSong
тип паров он показывает ошибки синтаксиса для всех вызовов.
Спасибо заранее.
Отредактировано, чтобы включить снимок экрана с ошибками, которые я получал в Eclipse Kepler SR2, которые, как я обнаружил, специфичны для Eclipse, потому что при компиляции с использованием java-компилятора JDK8 в командной строке он компилируется нормально.
t1
иt2
вObjects.compare
примере? Я пытаюсь вывести их, но сопоставить мой вывод типа над выводом типа компилятора невозможно. :-)Ответы:
Во-первых, все примеры, которые вы называете причиной ошибок, отлично компилируются с эталонной реализацией (javac из JDK 8.) Они также отлично работают в IntelliJ, поэтому вполне возможно, что ошибки, которые вы видите, специфичны для Eclipse.
Ваш основной вопрос, кажется: «почему он перестает работать, когда я начинаю цепочку». Причина в том, что в то время как лямбда-выражения и вызовы универсальных методов являются поли-выражениями (их тип зависит от контекста), когда они появляются как параметры метода, когда они появляются вместо выражений приемника метода, это не так.
Когда ты говоришь
информации о типе достаточно для решения как аргумента типа, так
comparing()
и типа аргументаp1
.comparing()
Вызов получает целевой тип с подписьюCollections.sort
, так как известно ,comparing()
должны возвращатьComparator<Song>
, и , следовательно ,p1
должно бытьSong
.Но когда вы начинаете цепочку:
теперь у нас проблема. Мы знаем, что у составного выражения
comparing(...).thenComparing(...)
есть целевой типComparator<Song>
, но поскольку выражение-получатель для цепочкиcomparing(p -> p.getTitle())
является вызовом общего метода, и мы не можем вывести параметры его типа из других аргументов, нам не повезло . Поскольку мы не знаем тип этого выражения, мы не знаем, что у него естьthenComparing
метод и т. Д.Есть несколько способов исправить это, и все они включают добавление дополнительной информации о типе, чтобы исходный объект в цепочке мог быть правильно типизирован. Вот они, в приблизительном порядке уменьшения желательности и увеличения навязчивости:
Song::getTitle
. Затем это дает достаточно информации о типе, чтобы вывести переменные типа дляcomparing()
вызова и, следовательно, присвоить ему тип и, следовательно, продолжить вниз по цепочке.comparing()
вызова:Comparator.<Song, String>comparing(...)
.Comparator<Song>
.источник
Comparator.<Song, String>comparing(...)
.Проблема заключается в определении типа. Без добавления
(Song s)
к первому сравнениюcomparator.comparing
не знает тип ввода, поэтому по умолчанию используется Object.Вы можете решить эту проблему одним из трех способов:
Используйте новый синтаксис ссылки на метод Java 8
Вытяните каждый шаг сравнения в локальную ссылку
РЕДАКТИРОВАТЬ
Принудительное использование типа, возвращаемого компаратором (обратите внимание, что вам нужен как тип ввода, так и тип ключа сравнения)
Я думаю, что "последняя"
thenComparing
синтаксическая ошибка вводит вас в заблуждение. На самом деле это проблема типа для всей цепочки, это просто компилятор отмечает только конец цепочки как синтаксическую ошибку, потому что, я думаю, когда окончательный тип возврата не соответствует.Я не уверен, почему
List
это делает работу логического вывода лучше, чемCollection
когда он должен делать тот же тип захвата, но, по-видимому, нет.источник
ArrayList
а не дляCollections
решения (учитывая, что первый вызов в цепочке имеетSong
параметр)?Другой способ справиться с этой ошибкой времени компиляции:
Явно укажите переменную вашей первой функции сравнения, и тогда все готово. У меня есть отсортированный список объекта 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());
источник
playlist1.sort(...)
создает границу Song для переменной типа E из объявления playlist1, которая "передается" компаратору.В
Collections.sort(...)
, такой границы нет, и вывода из типа первого компаратора недостаточно для компилятора, чтобы вывести остальные.Я думаю, вы получите "правильное" поведение
Collections.<Song>sort(...)
, но у вас нет установки java 8, чтобы проверить это для вас.источник