Подтвердить что ты не робот

Очень запутано в выводе типа 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

4b9b3361

Ответ 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());