Дуже заплутаний висновок типу 8 для порівняння


84

Я розглядав різницю між Collections.sortі list.sort, зокрема, щодо використання Comparatorстатичних методів та того, чи потрібні типи param в лямбда-виразах. Перш ніж ми почнемо, я знаю, що я міг би використовувати посилання на методи, наприклад, 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- майже протилежне тому, що відбувалося вище! Тоді як для всіх інших 3 прикладів, тобто з Objects.compare, List.sortі Collections.sortколи я видаляю цей перший Songпараметр, він показує синтаксичні помилки для всіх викликів.

Заздалегідь велике спасибі.

Відредаговано, щоб включити знімок екрана помилок, які я отримував у Eclipse Kepler SR2, які, як я зараз виявив, є специфічними для Eclipse, оскільки при компіляції за допомогою компілятора Java JDK8 у командному рядку він компілюється в порядку.

Сортувати помилки в Eclipse


Було б корисно, якщо ви включите у своє запитання всі повідомлення про помилки компіляції, які ви отримуєте у всіх своїх тестах.
Еран

1
чесно кажучи, я думаю, було б найпростіше для когось побачити, в чому проблема, запустивши сам вихідний код.
Спокій

Які є типи t1та t2у Objects.compareприкладі? Я намагаюся зробити висновок про них, але нашарування висновків мого типу над висновками типу компілятора є нерозв'язним. :-)
Стюарт Маркс

1
Також який компілятор ви використовуєте?
Стюарт Маркс

1
У вас є два окремі випуски сюди. Один із відповідачів зазначив, що ви можете використовувати посилання на методи, які ви сортували. Подібно до того, як лямбди подаються як із "явно набраним", так і з "неявно набраним", посилання на методи мають "точний" (одне перевантаження) та "неточне" (багаторазові перевантаження) аромати. Будь-який точний метод ref або явна лямбда може бути використана для надання додаткової інформації про введення тексту, якщо її немає. (Також можуть бути використані явні свідки та зліпки, але часто це більші молотки.)
Браян Гетц,

Відповіді:


105

По-перше, усі приклади, які ви кажете, спричиняють помилки, добре компілюються з посиланням на реалізацію (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>.

13
+1 за те, що фактично відповідає на ОП "чому компілятор не може зробити висновок про це", а не просто надає обхідні шляхи / рішення.
Джоффрі,

Дякую за вашу відповідь Брайане. Однак я все ще знаходжу щось без відповіді, чому List.sort поводиться інакше, ніж Collections.sort, оскільки перший вимагає, щоб перша лямбда містила тип параметра, а друга також вимагає, щоб останній, наприклад, якщо я маю порівняння за якими йдуть 5 тоді Порівняння дзвінків, які я повинен був би поставити (Пісня p1) у порівнянні та в останньому тоді Порівняння. Також у моєму початковому дописі ви побачите нижній приклад TreeSet, де я видаляю всі типи параметрів, і все-таки останній виклик thenComparing є нормальним, але інші ні, тому це поводиться інакше.
Спокій

3
@ user3780370 Ви все ще використовуєте компілятор Eclipse? Я не бачив такої поведінки, якщо правильно розумію ваше запитання. Чи можете ви (а) спробувати його за допомогою javac з JDK 8 і (б), якщо він все-таки не вдається, опублікувати код?
Брайан Гетц,

@BrianGoetz Дякую за цю пропозицію. Я щойно скомпілював його у вікні команд за допомогою javac, і він компілюється, як ви сказали. Здається, це проблема Eclipse. Я ще не оновився до Eclipse Luna, який спеціально створений для JDK8, тому, сподіваюся, це може бути виправлено в цьому. Я насправді маю скріншот, щоб показати вам, що відбувалося в Eclipse, але я не знаю, як розмістити тут.
Спокій

2
Гадаю, ти маєш на увазі Comparator.<Song, String>comparing(...).
shmosel

23

Проблема полягає у виведенні типу. Без додавання а (Song s)до першого порівняння, comparator.comparingвін не знає типу вхідних даних, тому за замовчуванням має значення Object.

Вирішити цю проблему можна одним із 3 способів:

  1. Використовуйте новий довідковий синтаксис методу Java 8

     Collections.sort(playlist,
                Comparator.comparing(Song::getTitle)
                .thenComparing(Song::getDuration)
                .thenComparing(Song::getArtist)
                );
    
  2. Витягніть кожен крок порівняння в локальну довідку

      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)
                );
    

    РЕДАГУВАТИ

  3. Примусовий тип, що повертається компаратором (зверніть увагу, що вам потрібні як тип введення, так і тип ключа порівняння)

    sort(
      Comparator.<Song, String>comparing((s) -> s.getTitle())
                .thenComparing(p1 -> p1.getDuration())
                .thenComparing(p1 -> p1.getArtist())
                );
    

Я думаю, що "остання" thenComparingсинтаксична помилка вводить вас в оману. Це насправді проблема типу з цілим ланцюжком, це просто компілятор, який лише позначає кінець ланцюжка як синтаксичну помилку, тому що тоді, напевно, кінцевий тип повернення не збігається.

Я не впевнений, чому Listроблю кращу роботу Collectionз виведення інформації, ніж оскільки він повинен робити той самий тип захоплення, але, мабуть, ні.


Чому він знає це для рішення, ArrayListале не для Collectionsрішення (якщо перший виклик у ланцюжку має Songпараметр)?
Sotirios Delimanolis

4
Дякую за вашу відповідь, однак, якщо ви прочитаєте мій пост, ви побачите, що я сказав: "Перш ніж ми почнемо, я знаю, що я міг би використовувати посилання на методи, наприклад, Song :: getTitle, щоб подолати свої проблеми, але мій запит тут не такий вже й великий щось, що я хочу виправити, але те, на що хочу отримати відповідь, тобто чому компілятор Java обробляє це таким чином ".
Спокій

Я хочу відповісти, чому компілятор поводиться так, коли я використовую лямбда-вирази. Він приймає порівняння (s -> s.getArtist ()), але тоді, коли я прив'язую .thenComparing (s -> s.getDuration ()), наприклад, це дає мені синтаксичні помилки для обох викликів, якщо я потім додаю явний тип в виклик порівняння, наприклад порівняння ((Пісні) -> s.getArtist ()), тоді це вирішує цю проблему, а для List.sort та TreeSet також вирішує всі подальші помилки компіляції без необхідності додавання додаткових типів параметрів, однак для the Collections.sort & Objects.com порівняти приклади останнього тоді Порівняння все-таки не вдається
Спокій

1

Інший спосіб вирішити цю помилку під час компіляції:

Надайте свою першу порівняльну змінну функції явно, а потім готово. У мене є сортування списку об’єкта 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());

0

playlist1.sort(...) створює прив'язку Song для змінної типу E, від оголошення списку відтворення1, який "пульсує" до компаратора.

У Collections.sort(...)Росії такої межі немає, і висновок щодо типу першого компаратора недостатній, щоб компілятор міг зробити висновок про решту.

Я думаю, ви отримаєте "правильну" поведінку Collections.<Song>sort(...), але не встановлюйте java 8, щоб перевірити це для вас.


привіт, так, ти маєш рацію у додаванні колекцій. <Song> справді позбавляється помилки для останнього порівняльного дзвінка
Tranquility
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.