Имеет ли ссылка на метод в Java 8 конкретный тип, и если да, то какой?

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

Итак, какой является тип ссылки на метод в Java 8? Вот небольшая демонстрация того, как ссылка на метод может быть «приведена» (поднята?) в java.util.function.Function:

package java8.lambda;

import java.util.function.Function;

public class Question {
  public static final class Greeter {
    private final String salutation;

    public Greeter(final String salutation) {
      this.salutation = salutation;
    }

    public String makeGreetingFor(final String name) {
      return String.format("%s, %s!", salutation, name);
    }
  }

  public static void main(String[] args) {
    final Greeter helloGreeter = new Greeter("Hello");

    identity(helloGreeter::makeGreetingFor)
      .andThen(g -> "<<<" + g + ">>>")
      .apply("Joe");

    //Compilation error: Object is not a function interface
//    Function
//      .identity()
//      .apply(helloGreeter::makeGreetingFor)
//      .andThen(g -> "<<<" + g + ">>>")
//      .apply("Joe");

    Function
      .<Function<String,String>>identity()
      .apply(helloGreeter::makeGreetingFor)
      .andThen(g -> "<<<" + g + ">>>")
      .apply("Joe");

    //Compilation error: Cannot resolve method 'andThen(<lambda expression>)'
//    (helloGreeter::makeGreetingFor)
//      .andThen(g -> "<<<" + g + ">>>")
//      .apply("Joe");

//    java.lang.invoke.LambdaMetafactory ???
  }

  private static <I,O> Function<I,O> identity(final Function<I,O> fun1) {
    return fun1;
  }
}

Итак, существует ли менее болезненный (более простой) способ приведения ссылки на метод к скомпилированному/конкретному типу, который можно передать?


person Andrey    schedule 26.04.2015    source источник
comment
Function<String, String> f = helloGreeter::makeGreetingFor; ? Выводится тип ссылки на метод. Одна и та же ссылка на метод может использоваться как Потребитель, Функция или другие функциональные интерфейсы в зависимости от контекста.   -  person JB Nizet    schedule 26.04.2015
comment
Если вы чувствуете, что на вопрос не был дан достаточно точный ответ, вы должны предложить вознаграждение за первоначальный вопрос, а не задавать его во второй раз. Есть ли способ, которым ваш вопрос отличается от предыдущего, или я могу закрыть его как дубликат?   -  person Dawood ibn Kareem    schedule 26.04.2015
comment
я полагаю, в сущности, они одинаковы. хотя ответы здесь кажутся более конкретными. я сам закрою.   -  person Andrey    schedule 26.04.2015
comment
Вопрос опровергает неверное предположение: все выражения имеют внутренний (восходящий) тип. Ссылки на методы и лямбда-выражения являются примерами поливыражений, выражений, тип которых зависит от их контекста. Тип ссылки на метод или лямбда — это просто тип, к которому он присваивается/приводится (при условии, что этот тип совместим с рассматриваемой ссылкой на лямбда/метод).   -  person Brian Goetz    schedule 26.04.2015
comment
@BrianGoetz, вы делаете хорошее замечание, что довольно удивительно в контексте строго типизированного языка, которым должна быть Java. Возможно, я слишком привык к подходу Scala, где каждый объект и выражение имеют конкретный тип, связанный с ним во время компиляции.   -  person Andrey    schedule 26.04.2015
comment
@ Андрей Твое удивление неуместно; каждое выражение в Java имеет конкретный тип времени компиляции. Новым здесь является то, что для некоторых выражений на этот тип может влиять (или требовать информацию о типе) контекст, в котором появляется выражение, например целевой тип присваивания. Лямбда-выражения и ссылки на методы имеют тип, но он определяется не самим выражением, а внешней информацией о типе (целевой тип присваивания или приведения, конечно, подлежит проверке на применимость).   -  person Brian Goetz    schedule 26.04.2015
comment
@BrianGoetz Мое удивление связано с тем, что левая часть задания определяет тип правой стороны (которая кажется обратной). в Scala в присваивании val fun = helloGreeter.makeGreetingFor _ правая часть имеет тип Function1[String,String], а тип fun предполагается таким же. более того, правая часть helloGreeter.makeGreetingFor _ сама по себе является допустимым, компилируемым выражением, потому что это просто объект однозначного типа Function1[String,String]...   -  person Andrey    schedule 27.04.2015


Ответы (4)


Прежде всего, ссылки на методы «представляют собой компактные, легко читаемые лямбда-выражения для методов, у которых уже есть имя» (см. Учебники по Java — справочники по методам).

Так что на самом деле вы запрашиваете тип лямбда-выражения. Это ясно объясняется в JLS § 15.27.3 (Тип лямбда-выражения).

Короче говоря, упоминаются три совместимости:

  1. Контекст назначения
  2. Контекст вызова
  3. Кастинг контекста

Тип лямбда-выражения или ссылки на метод определяется компилятором. Поскольку теперь можно (и нужно) учитывать несколько контекстов, в Java 8 были внесены большие улучшения для вывода типов.

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

person Seelenvirtuose    schedule 26.04.2015

Из JLS, раздел 15.13.2, "Тип Справочник по методу":

Выражение ссылки на метод совместимо в контексте назначения, контексте вызова или контексте приведения с целевым типом T, если T является типом функционального интерфейса (§9.8) и выражение соответствует типу функции наземного целевого типа, полученного из T .

...

Если выражение ссылки на метод совместимо с целевым типом T, то тип выражения U является наземным целевым типом, производным от T.

По сути, тип ссылки на метод — это то, что контекст ожидает от него. Независимо от контекста ссылка на метод на самом деле не имеет типа. Невозможно передать ссылку на «сырой» метод, а затем превратить ее в функцию, потребителя или что-то еще в какой-то более поздний момент.

person user2357112 supports Monica    schedule 26.04.2015

Ссылки на методы — это просто синтаксический сахар для функции, которая использует переданный параметр в качестве входного аргумента. Таким образом, вы можете назначить их следующим образом:

Runnable runnable = System.out::println;
Consumer consumer = System.out::println;

типы выводятся и зависят от контекста.

Ваш случай:

Function<String, String> foo = helloGreeter::makeGreetingFor;

и он равен:

Function<String, String> foo = s -> helloGreeter.makeGreetingFor(s);
person Grzegorz Piwowarek    schedule 26.04.2015

Если у вас есть только ссылка на метод helloGreeter::makeGreetingFor, она не имеет типа.

Если вы хотите присвоить тип ссылке на метод, не назначая его или не передавая в качестве аргумента (что присваивает его параметру), вы можете привести его:

String greeting =
    ((Function<String, String>)helloGreeter::makeGreetingFor)
        .apply("Joe");
person Radiodef    schedule 26.04.2015