Arhn - архитектура программирования

Компилятор не выводит функциональный интерфейс System.out::println

У меня есть перегруженный метод, который принимает в качестве параметров два разных функциональных интерфейса (Runnble и Supplier). System.out.println явно совместим только с Runnable, потому что это метод void. Однако компилятор по-прежнему утверждает, что вызов неоднозначен. Как это возможно?

import java.util.function.Supplier;

public class GenericLambdas {
    public static void main(String[] args) {
        wrap(System.out::println);   // Compiler error here
        wrap(() -> {});              // No error
        wrap(System.out::flush);     // No error
    }

    static <R> void wrap(Supplier<R> function) {}

    static void wrap(Runnable function) {}
}

Вывод компилятора:

Error:Error:line (5)java: reference to wrap is ambiguous
    both method <R>wrap(java.util.function.Supplier<R>) in GenericLambdas and method wrap(java.lang.Runnable) in GenericLambdas match 
Error:Error:line (5)java: incompatible types: cannot infer type-variable(s) R
    (argument mismatch; bad return type in method reference
      void cannot be converted to R)

Основываясь на второй ошибке (argument mismatch, void cannot be converted to R), не должен ли компилятор устранить неоднозначность вызова? Тогда это позаботится об обеих ошибках компилятора (поскольку он не будет двусмысленным и не попытается преобразовать void в R).

И почему () -> {} и System.out::flush могут разрешить? У них та же подпись, что и у System.out.println. Допустим, что System.out.println перегружен версиями, принимающими аргумент, но ни одна из этих перегруженных версий не соответствует ни Supplier, ни Runnable, так что я не вижу, как они могут быть здесь уместны.

РЕДАКТИРОВАТЬ:

Кажется, что это действительно компилируется и запускается с Компилятор Eclipse. Какой компилятор правильный или разрешено любое поведение?


  • Он отлично компилируется, когда я копирую и вставляю код. 14.10.2015
  • Как правило, перегрузка несколькими функциональными интерфейсами почти всегда плохая идея. Просто используйте другое имя метода. 14.10.2015
  • Воспроизведено в командной строке javac из JDK 1.8.0_60. В Eclipse он компилируется без двусмысленности. 14.10.2015
  • @YassinHajaj, какой компилятор вы используете? 14.10.2015
  • SE-1.8, кажется. @SkinnyJ 14.10.2015
  • @YassinHajaj javac из командной строки или внутри IDE? 14.10.2015
  • Внутри IDE он не выдает мне сообщения об ошибке и также выполняется. Я на Eclipse и использую Java 8, а компилятор - Java SE 1.8. 14.10.2015
  • imgur.com/VaSAPDt Вот принтскрин Eclipse @SkinnyJ ... Какой компилятор вы видите? 14.10.2015
  • @YassinHajaj попробовал это с Oracle JDK 1.8.0_40 и 1.8.0_60, получил ошибку в обоих случаях. 14.10.2015
  • @SkinnyJ Как видите, у меня он отлично компилируется. Скажите мне, какая информация вам нужна, и я предоставлю ее. 14.10.2015
  • Я только что подтвердил, что изменение компилятора в IntelliJ на компилятор Eclipse позволяет компилировать и пробег. Интересно, что правильно. Я отредактирую вопрос. 14.10.2015
  • Еще один интересный момент: если вместо println подставить System.out::flush, то ошибка исчезнет. Итак, проблема связана с тем, что println перегружен. 14.10.2015
  • @RealSkeptic У меня возникло подозрение, когда я увидел, что анонимная лямбда скомпилирована. Но как перегруженные версии могут иметь отношение к Runnable или Supplier, ни одна из которых не принимает никаких параметров? 15.10.2015
  • Компилятор должен выяснить, какая версия println будет использоваться, и тип возвращаемого значения, очевидно, не играет здесь никакой роли. Я думаю, что это соответствует спецификации, но я не совсем уверен. Имейте в виду, что возвращаемый тип также не является частью сигнатуры метода. Таким образом, с этой точки зрения и Supplier#get, и Runnable#run будут совпадать. 15.10.2015
  • Это может быть неочевидным дубликатом stackoverflow.com/questions/29323520/ ... 15.10.2015
  • @Marco13 Marco13 Думаю, вы правы bugs.java.com/bugdatabase/view_bug. сделать?bug_id=8077243 29.10.2015

Ответы:


1

При поиске подходящей версии println тип возвращаемого значения игнорируется. См. JLS 15.13.2. Включать его не имеет смысла, потому что не может быть двух версий метода с одинаковыми параметрами, но с разным типом возвращаемого значения. Но теперь у компилятора проблема: и Supplier#get, и Runnable#run ожидают одни и те же параметры (ни одного). Итак, есть println, который подходит для обоих. Имейте в виду, что на этом этапе компилятор только пытается найти метод. Компилятор в основном сталкивается с той же проблемой, что и в этом коде:

public static void main(String[] args) {
 test(System.out::println);
}

public static void test(Runnable r) {}
public static void test(Consumer<String> r) {}

println() соответствует Runnable#run, а println(String) соответствует Consumer#accept. Мы не указали тип цели, так что ситуация неоднозначная.

После выбора метода целевой тип может быть правильно выведен, и на этом этапе имеет значение возвращаемый тип. См. JLS 15.13.2. Таким образом, этот код, конечно, потерпит неудачу:

public static void main(String[] args) {
    wrap(System.out::println);
}

static <R> void wrap(Supplier<R> function) {}

Компилятор немедленно выдает ошибку, когда обнаруживает двусмысленность. Хотя отчет об ошибке был создан и принят для такого поведения, комментарии это указывает на то, что Oracle JDK может более точно придерживаться JLS, чем ECJ (несмотря на его более приятное поведение). Более поздний отчет об ошибке, поднятый после этого вопроса SO , было решено как "Не проблема", что указывает на то, что после внутреннего обсуждения Oracle решил, что поведение javac является правильным.

15.10.2015
  • Разве то же самое не относится к System.out::flush? Однако этот выбирает правильную (Runnable) перегрузку. 15.10.2015
  • @SkinnyJ Я предполагаю, что если есть только один метод с этим идентификатором, поиск не требуется. Итак, процесс, описанный в JLS 15.13.1. не будет применяться, и компилятор может сразу определить целевой тип. 15.10.2015
  • Также странно, что компилятор выдает обе ошибки. Основываясь на том, как вы описали это выше, я думаю, что, как только он решит, что println неоднозначен, он также не будет жаловаться на тип возвращаемого значения. Если он жалуется на плохой тип возвращаемого значения, это означает, что он уже сопоставил println с Supplier. 15.10.2015
  • @SkinnyJ Компилятор не решил использовать версию Supplier, но хочет создать типизированную версию <R> wrap. Это может быть ошибка. С другой стороны, обе ошибки происходят в одной и той же строке. Измените порядок методов wrap, и вторая ошибка исчезнет. 15.10.2015
  • очаровательный. Это, конечно, кажется странным. Итак, на данный момент вы говорите, что ошибка неоднозначности является ограничением (возможной ошибкой) из-за того, что правила для перегруженных методов проверяются до правил вывода функционального интерфейса, а ошибка типа возвращаемого значения, возможно, является ошибкой, но не так актуальна, потому что это само по себе не помешало бы компиляции? Это звучит разумно. 15.10.2015
  • @SkinnyJ: это не новая функция. Компилятор не останавливается на первой ошибке и в процессе сообщения о дополнительных ошибках не распознает последующие ошибки. Вы можете легко получить двадцать ошибок, просто опуская одну фигурную скобку или запятую (или вставляя их не в том месте)… 15.10.2015
  • @Holger Я был очарован тем фактом, что изменение порядка определений методов привело к исчезновению ошибки. 15.10.2015
  • Оглядываясь назад на это пару недель спустя, я думаю, что комментарий @Marco13 и этот отчет об ошибке совершенно правильны (что в основном то, что вы объяснили). Судя по примечаниям в отчете об ошибках, даже инженеры Oracle не могут прийти к единому мнению, является ли это ошибкой или точным соблюдением JLS. bugs.java.com/bugdatabase/view_bug.do?bug_id=8077243 29.10.2015

  • 2

    Похоже на ошибку в javac компиляторе. Проблема заключается в перегруженном методе println(). Он имеет разные реализации в зависимости от типа, который вы пишете: int, long, String и т. д. Таким образом, выражение: System.out::println имеет 10 методов на выбор. Один из них может быть выведен до Runnable, 9 других до Consumer<T>.

    Каким-то образом компилятор javac не может вывести правильную реализацию метода из этого выражения. И wrap(() -> {}) компилируется правильно, потому что это выражение имеет только одну возможную интерпретацию — Runnable.

    Я не уверен, разрешено ли иметь такие выражения по правилам JLS. Но следующий код правильно компилируется с использованием javac (и работает без проблем во время выполнения):

    wrap((Runnable)System.out::println);
    

    Похоже, что это приведение предоставляет компилятору необходимую информацию для правильного определения типа, что довольно странно. Я не знал, что выражения приведения можно использовать в выводе типов.

    15.10.2015
  • Отправил отчет об ошибке в Oracle. Посмотрим, что они скажут. 15.10.2015
  • @SkinnyJ, я не уверен на 100%, но, вероятно, это ожидаемое поведение. Когда JLS описывает разрешение ссылки на метод, он говорит о сопоставлении аргументов, но ничего о сопоставлении возвращаемого типа. Таким образом, вполне вероятно, что при неоднозначности аргументов должна возникать ошибка компилятора, даже если тип возвращаемого значения может устранить неоднозначность. 15.10.2015
  • @TagirValeev в таком случае, почему () -> {} разрешается нормально? Если вы проигнорируете возвращаемый тип, он также будет соответствовать как Runnable, так и Supplier. 15.10.2015
  • @SkinnyJ, потому что лямбда не является ссылкой на метод, и вам не нужно разрешать лямбда-выражение, поскольку указано одно точное выражение. Существуют лямбда-выражения, совместимые со значениями и с пустотой, и их тип возвращаемого значения имеет значение во время разрешения вызова внешнего метода, см. здесь. 15.10.2015
  • @TagirValeev как насчет System.out::flush? 15.10.2015
  • @SkinnyJ: это объясняется в вопросе, Marco13 связался с. Проблема с wrap(…::println) заключается в том, что компилятору нужен целевой тип, чтобы выяснить, какой метод println использовать, но он не может решить, потому что не знает, какой метод wrap использовать. В случае flush такой проблемы нет, так как есть только один метод flush. Один метод означает отсутствие двусмысленности, поэтому функциональная сигнатура известна и может использоваться для выбора подходящего wrap метода. 15.10.2015
  • @TagirValeev нет, не будет. Компилятор Java не игнорирует возвращаемый тип при сопоставлении сигнатур методов, а также не выполняет преобразование между типами void и java.lang.Void. Вот почему () -> {} имеет только одну интерпретацию. Вы легко можете проверить это сами: Supplier<Void> a = () -> {};. Как вы правильно заметили, это два разных типа лямбда-выражений: совместимые с пустотой и совместимые со значением. 16.10.2015
  • К вашему сведению здесь отчет об ошибке, который я отправил 09.12.2015
  • Новые материалы

    Коллекции публикаций по глубокому обучению
    Последние пару месяцев я создавал коллекции последних академических публикаций по различным подполям глубокого обучения в моем блоге https://amundtveit.com - эта публикация дает обзор 25..

    Представляем: Pepita
    Фреймворк JavaScript с открытым исходным кодом Я знаю, что недостатка в фреймворках JavaScript нет. Но я просто не мог остановиться. Я хотел написать что-то сам, со своими собственными..

    Советы по коду Laravel #2
    1-) Найти // You can specify the columns you need // in when you use the find method on a model User::find(‘id’, [‘email’,’name’]); // You can increment or decrement // a field in..

    Работа с временными рядами спутниковых изображений, часть 3 (аналитика данных)
    Анализ временных рядов спутниковых изображений для данных наблюдений за большой Землей (arXiv) Автор: Рольф Симоэс , Жильберто Камара , Жильберто Кейрос , Фелипе Соуза , Педро Р. Андраде ,..

    3 способа решить квадратное уравнение (3-й мой любимый) -
    1. Методом факторизации — 2. Используя квадратичную формулу — 3. Заполнив квадрат — Давайте поймем это, решив это простое уравнение: Мы пытаемся сделать LHS,..

    Создание VR-миров с A-Frame
    Виртуальная реальность (и дополненная реальность) стали главными модными терминами в образовательных технологиях. С недорогими VR-гарнитурами, такими как Google Cardboard , и использованием..

    Демистификация рекурсии
    КОДЕКС Демистификация рекурсии Упрощенная концепция ошеломляющей О чем весь этот шум? Рекурсия, кажется, единственная тема, от которой у каждого начинающего студента-информатика..