Java 8 streams, почему это компилирует часть 2... или что такое ссылка на метод, на самом деле?


Хорошо, первый вопрос в этой "серии" был Этот.

Теперь, вот еще один случай:
Arrays.asList("hello", "world").stream().forEach(System.out::println);
Это компилирует и работает... Хорошо, в последнем вопросе использовались статические методы из класса .

Но теперь это другое: System.out - это static поле System, Да; это также PrintStream, и у PrintStream есть метод println(), который в этом случае совпадает с сигнатурой Consumer, и a Consumer - это то, что forEach() ожидает .

Так что я попробовал это сделать...
public final class Main
{
    public static void main(final String... args)
    {
        Arrays.asList(23, 2389, 19).stream().forEach(new Main()::meh);
    }

    // Matches the signature of a Consumer<? super Integer>...
    public void meh(final Integer ignored)
    {
        System.out.println("meh");
    }
}

И это работает!

Здесь совсем другая область, так как я инициирую новый экземпляр и могу использовать ссылку на метод сразу после создания этого экземпляра!

Итак, является ли ссылка на метод действительно любым методом, который подчиняется сигнатуре? Каковы же эти пределы? Есть ли случаи, когда можно построить метод "@ Functional Interface compatible", который не может использоваться в @FunctionalInterface?
3 9

3 ответа:

Синтаксис ссылок на методы определен в JLS #15.13. В частности, он может иметь вид:

Primary :: [TypeArguments] Identifier

Где Primary может быть, среди прочего, a:

ClassInstanceCreationExpression

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

this::someInstanceMethod    // (...) -> this.someInstanceMethod(...)
"123"::equals               // (s) -> "123".equals(s)
(b ? "123" : "456")::equals // where b is a boolean
array[1]::length            // (String[] array) -> array[1].length()
String[]::new               // i -> new String[i]
a.b()::c                    // (...) -> a.b().c(...)

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

class Static { static void m() {} }
Static s = new Static();

s.m(); //compiles
someStream.forEach(s::m); //does not compile
someStream.forEach(Static::m); //that's ok

Из состояния лямбды

Виды ссылок на методы

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

  • статический метод (ClassName::methName)
  • метод экземпляра конкретного объекта (instanceRef::methName)
  • супер метод конкретного объекта (super::methName)
  • метод экземпляра произвольного объекта определенного типа (ClassName::methName)
  • класс ссылка на конструктор(ClassName::new)
  • ссылка на конструктор массива (TypeName[]::new)

Говоря это:

something(new Main()::meh);

Примерно равносильно тому, чтобы сказать следующее:

Main x = new Main();
something(() -> x.meh());

Или вот это:

final Main x = new Main();
something(new Whatever() {
    public void meh(Integer ignored) {
        x.meh();
    }
}

Новый экземпляр "захватывается" и используется в новом экземпляре lambda, который был неявно создан из дескриптора метода.