Очень запутано в выводе типа Java 8 Comparator
Я рассматривал разницу между Collections.sort
и list.sort
, особенно в отношении использования статических методов Comparator
, и требуются ли типы параметров в выражениях лямбда. Прежде чем мы начнем, я знаю, что могу использовать ссылки на методы, например. Song::getTitle
, чтобы преодолеть мои проблемы, но мой запрос здесь - это не столько что-то, что я хочу исправить, а то, на что я хочу получить ответ, то есть почему компилятор Java обрабатывает его таким образом.
Это мои находки. Предположим, что у нас есть ArrayList
типа Song
, с добавленными песнями есть 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())
);
Теперь вот часть CONFUSING. Для p laylist1.sort
, то есть List, это решает все ошибки компиляции для обоих следующих вызовов 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
- почти что противоположно тому, что происходило выше! Принимая во внимание, что для всех остальных 3 примеров, т.е. С Objects.compare
, list.sort
и Collections.sort
, когда я удаляю этот первый тип параметра Song
, он показывает синтаксические ошибки для всех вызовов.
Большое спасибо заранее.
Отредактировано, чтобы включить скриншот ошибок, которые я получал в Eclipse Kepler SR2, которые с тех пор я нашел, являются специфичными для Eclipse, потому что при компиляции JCK8 java-компилятора в командной строке он компилирует ОК.
![Sort errors in Eclipse]()
Ответы
Ответ 1
Во-первых, все примеры, которые вы говорите, приводят к ошибкам, компилируются с помощью эталонной реализации (javac от JDK 8.) Они также отлично работают в IntelliJ, поэтому вполне возможно, что ошибки, которые вы видите, являются специфичными для Eclipse.
Ваш основной вопрос, кажется, "почему он перестает работать, когда я начинаю цепочку". Причина в том, что лямбда-выражения и общие вызовы методов - это поли-выражения (их тип чувствителен к контексту), когда они появляются как параметры метода, когда они появляются в качестве выражений приемника метода, они не являются.
Когда вы говорите
Collections.sort(playlist1, comparing(p1 -> p1.getTitle()));
существует достаточная информация о типе для решения как аргумента типа comparing()
, так и типа аргумента p1
. Вызов comparing()
получает свой целевой тип из сигнатуры Collections.sort
, поэтому известно, что comparing()
должен возвращать 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>
.
Ответ 2
Проблема заключается в вызове типа. Не добавляя (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())
);
Я думаю, что синтаксическая ошибка "last" thenComparing
вводит вас в заблуждение. Это на самом деле проблема типа со всей цепочкой, просто компилятор только отмечает конец цепочки как синтаксическую ошибку, потому что это, когда окончательный тип возвращаемого значения не совпадает, я думаю.
Я не уверен, почему List
выполняет лучшую работу по определению, чем Collection
, поскольку он должен делать один и тот же тип захвата, но, по-видимому, нет.
Ответ 3
playlist1.sort(...)
создает привязку Песни для переменной типа E из объявления списка воспроизведения1, который "рябит" к компаратору.
В Collections.sort(...)
такой привязки нет, и вывод из типа первого компаратора недостаточен для того, чтобы компилятор вывел остальное.
Я думаю, что вы получите "правильное" поведение от Collections.<Song>sort(...)
, но не имеете установки java 8, чтобы проверить его для вас.
Ответ 4
Другой способ справиться с этой ошибкой времени компиляции:
Вставьте свою первую переменную функции сравнения в явном виде, а затем хорошо перейти. У меня есть список объектов 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());