Использование параметра типа в справочнике по методам Java

В Java Precisely 3rd Ed. есть следующий фрагмент кода:

BiConsumer<Double[], Comparator<Double>> arraySorter = Arrays::<Double>sort;

Однако я заметил, что даже когда я пропускаю <Double> после ::, ссылка на метод все еще действительна (что имеет смысл из-за параметров типа для BiConsumer).

Тем не менее, я довольно запутался в том, есть ли случаи, в которых ::<T> будет необходимым в ссылке на метод, и если да, то пример был бы очень полезен.


person Zz'Rot    schedule 17.03.2018    source источник
comment
Компилятор Java стал «умнее». Предполагая, что FooBar является производным от Foo: в Java7: List<Foo> foo = Arrays.asList(new FooBar()); не будет компилироваться, но будет компилироваться в Java8. Java7 нуждается в параметре типа: List<Foo> foo = Arrays.<Foo>asList(new FooBar());.   -  person AJNeufeld    schedule 17.03.2018
comment
Нам нужен эксперт по Спецификации языка Java (JLS), который может указать нам точный раздел JLS, в котором полностью описана вся казуистика для вывода типов для параметров универсального типа методов, а также его связь со ссылками на методы. . Честно говоря, вместо того, чтобы изучать все случаи, когда вывод работает или не работает, я думаю, что лучше попробовать и посмотреть, сможет ли компилятор самостоятельно вывести параметры типа; если нет, то мы можем указать параметры типа с помощью нотации ::<T>.   -  person fps    schedule 17.03.2018


Ответы (3)


Я полагал, что вывод типа локальной переменной Java 10 (var name = ...;) будет ответом на эту загадку. Вместо того, чтобы тип переменной назначения предоставлял тип для ссылки на метод, в правой части необходимо было бы полностью указать тип, что потребовало бы параметра типа (::<T>) в ссылке на метод.

Сначала придумали ворота...

var arraySorter = Arrays::<Double>sort;

... но ссылки на методы сами по себе не определяют тип. Они должны быть преобразованы компилятором в функциональный объект, и компилятор не будет искать известные функциональные интерфейсы в поисках соответствующего типа, даже если он был ровно один.


Следующей мыслью было использовать ссылку на метод в качестве аргумента метода, который возвращает тип на основе аргумента метода.

class Spy {
    static <T> Function<T,T> f2(Function<T,T> f) {
        return f.andThen(f);
    }

    static <T> T identity(T t) {
        return t;
    }
}

Используя это, мы можем создать нашу локальную переменную, передав ссылку на наш метод:

Function<Double,Double> double_identity = f2(Spy::<Double>identity);

Как и ожидалось, мы можем удалить ::<Double>

Function<Double,Double> double_identity = f2(Spy::identity);

Неожиданно, с выводом типа локальной переменной все в порядке.

var double_identity = f2(Spy::identity);             // Infers <Object>!
Object obj = null;
double_identity.apply(obj); 

Но настоящий сюрприз возникает, когда для его переопределения используется ссылочный тип метода.

var double_identity = f2(Spy::<Double>identity);     // Error: Double != Object

Немного поругавшись, я понял, почему. Мы должны применить тип к самому методу f2:

var double_identity = Spy.<Double>f2(Spy::identity); // Works.

Оглядываясь назад, это имеет некоторый смысл. Тип переменной обычно обеспечивает контекст для внешней функции. Присвоение результата переменной Function<Double,Double> позволяет компилятору определить тип f2(...), который затем передает этот тип в аргументы. С var name = ... без явного типа доступен только тип Object, поэтому компилятор выводит Spy.<Object>f2(...), а затем определяет, что тип аргумента должен быть Function<Object,Object>.

К сожалению, кажется, что он не анализирует изнутри, так что Spy::<Double>identity не приводит к тому, что функция выводится как Spy.<Double>f2(...), а переменная - как Function<Double,Double>. Может Ява 11? Возможно, он слишком сильно сломается и не сможет работать.

Однако это положило конец моим попыткам злоупотребить var name = ...;, чтобы решить загадку ОП.


Большое спасибо @Eugene за критику моих предыдущих попыток до выпуска Java 10.

person AJNeufeld    schedule 17.03.2018
comment
Мне это почти понравилось... но var listMaker = Arrays::<Double>asList; не сработало бы, потребовался бы явный целевой тип - person Eugene; 18.03.2018
comment
@ Евгений Должен признаться, я на самом деле не пробовал. Официальный релиз Java 10 состоится через 3 дня. Но я удивлен, что это не сработает. Существует только 1 метод Arrays::asList (<T> List<T> Arrays::asList(T...)). Он должен работать; если это не так, я должен спросить, почему нет, потому что это звучит как ошибка, если это не так. - person AJNeufeld; 18.03.2018
comment
@Eugene Хорошо, теперь я понял. Какой тип listMaker? Это Function<T[],List<T>>? Это имело бы смысл для меня, но будет ли компилятор искать все известные @FunctionalInterface, чтобы найти класс с единственным абстрактным методом, который соответствует данной сигнатуре? Что, если он найдет более одного? listMaker.getClass() должен вернуть конкретный результат, а если есть двусмысленность, то не может. Таким образом, хотя var xxx = ... отлично работает с методом, который возвращает конкретный тип, ссылка на метод не имеет конкретного типа и может быть совместима с несколькими. И я был так близок... - person AJNeufeld; 18.03.2018
comment
@JNeufeld действительно, да, вы были близки ... компилятор ничего не будет искать (это добавит накладные расходы, если он это сделает), он должен вывести тип на месте, и, кстати, var - это просто вещь компилятора, байтовый код то же самое с ним или с переменными pre-java-10 типа List... - person Eugene; 18.03.2018
comment
@ Евгений, кажется, я понял. Я добавил вызов функции, которая принимает ссылку на метод и возвращает тип, основанный на типе ссылки на метод, и присваивает возвращаемое значение локальной переменной var. Можешь подтвердить? Или развенчать? Или улучшить? - person AJNeufeld; 18.03.2018
comment
нет, отлично работает без необходимости добавлять <Double>, просто он выводится как Function<Object, Object>.. - person Eugene; 19.03.2018
comment
Мне очень жаль, что я не проголосовал за это, кстати, это было очень хорошее испытание, может быть, лучшее в этом вопросе. заслуженный плюс один! - person Eugene; 31.03.2018
comment
@Zz'Rot Я ценю +1, но я думаю, что зеленая галочка заходит слишком далеко. Я не нашел ответа на загадку и не доказал ее невозможность. Вероятно, вам следует оставить вопрос открытым, пока кто-нибудь не даст окончательный ответ. - person AJNeufeld; 21.05.2018

Это вывод типов в действии, в большинстве случаев компилятор выведет типы за вас, что означает, что вам не нужно указывать их явно.

Тем не менее, есть определенные обстоятельства, в которых вам необходимо вручную предоставить подсказки типа.

В любом случае, Java 8 улучшает вывод общих аргументов.

Итак, делаем следующее:

BiConsumer<Double[], Comparator<Double>> arraySorter = Arrays::sort;

в полностью допустимом из-за вывода типа.

Пара примеров, которые я могу придумать прямо сейчас, которые не работали бы в Java-7, но работают в Java-8, выглядят примерно так:

void exampleMethod(List<Person> people) {
      // do logic
} 

exampleMethod(Collections.emptyList())

Другой пример:

someMethodName(new HashMap<>());
...
void someMethodName(Map<String, String> values);

Ранее от вас требовалось явно указать аргументы типа.

Кроме того, из-за вышеупомянутого вывода типа это точная причина, по которой мы теперь можем сделать что-то вроде:

...
...
.collect(Collectors.toList());

вместо этого парня:

...
...
.collect(Collectors.<Person>toList());

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

person Ousmane D.    schedule 17.03.2018
comment
хорошо для меня, это хорошо, но не отвечает на вопрос как таковой. ::<T> - это то, что ищет ОП, и, читая ваш ответ, я действительно думал, что вы нашли такой случай... - person Eugene; 17.03.2018
comment
@Eugene Однако я заметил, что даже когда я опускаю ‹Double› после ::, ссылка на метод все еще действительна (что имеет смысл из-за параметров типа для BiConsumer). , я объяснил причину к этому. что касается Тем не менее, я довольно запутался в том, есть ли случаи, когда ::‹T› было бы необходимо в ссылке на метод, и если да, то пример был бы очень полезен., я не могу подумайте еще о примере, где, если целевой тип BiConsumer<T, U>, и вы вынуждены явно указывать аргументы типа для вещи справа. - person Ousmane D.; 17.03.2018
comment
точно, пока. Я пытался даже найти ::< внутри источников jvm 8,9 и 10, чтобы найти пример такого, но пока безуспешно. - person Eugene; 17.03.2018
comment
@Eugene, поскольку я не могу придумать другого примера, где, если целевой тип равен BiConsumer<T, U>, и вы вынуждены явно указывать аргументы типа для вещи справа. Вместо этого я показал пару примеров, где вы были бы вынуждены предоставить аргументы типа, чтобы они работали до Java-8. Хотя, возможно, это не точный пример, за которым следует OP, он достаточно близок, чтобы показать, где вы должны явно указать аргументы типа. В целом, я думаю, что это устраняет двусмысленность OP в отношении того, что явно означает предоставление аргументов типа. - person Ousmane D.; 17.03.2018
comment
конечно ... Я даже думаю, что ОП не знает, что это меня так сильно тронуло, дело не только в BiConsumer, мне интересен любой пример, где потребуются дополнительные аргументы типа со ссылкой на метод (!= лямбда, с лямбдой легко) - person Eugene; 17.03.2018
comment
@Eugene Хорошо, пингуйте меня, если вам удастся привести пример, мне было бы интересно посмотреть. ;) - person Ousmane D.; 17.03.2018
comment
ну, это был не я (и это не желаемый результат), но он наиболее близок к реальной достаточно близкой ИМО по этому вопросу stackoverflow. com/a/49340725/1059372 - person Eugene; 31.03.2018

Java 8 реализует обобщенный вывод целевого типа (JEP 101), который позволяет компилятору определять < em>параметр типа универсального метода. В вашем примере компилятор Java 8 выводит параметр типа метода sort из правой части присваивания.

JEP 101 также предлагал общий вывод целевого типа для связанных вызовов методов, но он не был реализован, потому что сложности, которую он внес бы в алгоритм вывода (обсуждается здесь и здесь). Таким образом, связанные вызовы универсального метода являются примером, в котором параметр типа универсального метода не может быть выведен.

Рассмотрим следующий фрагмент кода:

class Main {

    String s = MyList.nil().head(); // Incompatible types. Required: String. Found: Object.

    static class MyList<E> {
        private E head;
        static <Z> MyList<Z> nil() { return new MyList(); }
        E head() { return head; }
    }
}

Компилятору не удается вывести параметр типа для универсального метода nil() в String s = MyList.nil().head(). Итак, мы должны предоставить больше информации алгоритму вывода, либо добавив параметр типа

String s = MyList.<String>nil().head();

или путем разделения связанных вызовов

MyList<String> ls = MyList.nil();
String s = ls.head();

Примечание. Пример цепных вызовов не содержит ссылки на общий метод (синтаксис ::<>), как в исходном вопросе, но метод вывода, используемый в обоих примерах, одинаков. Следовательно, ограничения вывода также одинаковы.

person SergiyKolesnikov    schedule 17.03.2018