Являются ли ямбады Java 8 компилируемыми как внутренние классы, методы или что-то еще?

Сегодня я прочитал эту статью относительно лямбда:

http://www.infoq.com/articles/Java-8-Lambdas-A-Peek-Under-the-Hood

В статье предлагается, что lambdas не реализованы как внутренние внутренние классы (из-за производительности). Это дает пример того, что выражение лямбда может быть скомпилировано как (статический) метод класса.

Я пробовал очень простой фрагмент:

private void run() {
    System.out.println(this);
    giveHello(System.out::println);
}

private void giveHello(Consumer<String> consumer) {
    System.out.println(consumer);
    consumer.accept("hello");
}

а выход:

[email protected]
sample.Main$$Lambda$1/[email protected]
hello

Итак, это не тот же самый экземпляр. Это не какой-то центральный экземпляр "Lambda Factory".

Как реализованы lambdas?

Ответ 1

Само выражение, считая, что вы передаете фактическое лямбда-выражение, а не ссылку на метод, скомпилировано как отдельный синтетический метод. В дополнение к любым формальным аргументам ожидаемого функционального интерфейса (например, одиночный String в случае Consumer<String>) он будет включать аргументы для любых зафиксированных значений.

В месте кода, где появляется лямбда-выражение или ссылка на метод, выдается команда invokedynamic. При первом нажатии этой команды вызов выполняется в методе начальной загрузки на LambdaMetafactory. Этот метод начальной загрузки будет фиксировать фактическую реализацию целевого функционального интерфейса, который делегирует целевой метод, и это то, что возвращается. Целевой метод - это либо синтетический метод, представляющий тело лямбда, либо какой-либо именованный метод был предоставлен с использованием оператора ::. В то время как класс, который реализует функциональный интерфейс, создается, процесс откладывается; это не происходит во время компиляции.

Наконец, среда выполнения исправляет сайт invokedynamic с результатом начальной загрузки 1 что фактически является вызовом конструктора для сгенерированного делегата с любыми пройденными значениями, включая (возможно) цель вызова 2. Это уменьшает производительность, удаляя процесс начальной загрузки для последующих вызовов.


1 См. java.lang.invoke конец главы "Время привязки" , любезно предоставлено @Holger.

2 В случае lambda, который не захватывает, команда invokedynamic обычно будет разрешать общий экземпляр делегирования, который может быть повторно использован во время последующих вызовов, хотя это деталь реализации.

Ответ 2

Я задал себе тот же вопрос и нашел это видео, разговор Брайана Гетца. Это очень полезное введение в том, как lambdas реализованы в java.

Изменить (резюме): Посмотрел его некоторое время назад, так что это может быть не совсем правильно. Когда файлы компилируются, компилятор оставляет описание того, что должен делать лямбда. Затем JRE, когда он запускает код, решает, как реализовать лямбду. Существует несколько вариантов: встроенный, метод, анонимный класс. Затем он будет использовать динамическое распределение для распределения реализации.

Ответ 3

Lambda в Java 8 - это так называемые функциональные интерфейсы, то есть анонимные интерфейсы с одним методом default.

Ответ 4

Я не уверен в этом, но я верю, что он просто компилирует анонимный внутренний класс. Consumer<T> - это интерфейс, поэтому я бы сказал, что ваш пример почти равен

    giveHello(new Consumer<String>() {

        @Override
        public void accept(String t) {
            System.out.println(t);
        }
    });

ИЗМЕНИТЬ

после некоторых исследований выше ответ не является полным и действительным. Лямбда-выражения могут быть переведены на анонимный внутренний класс, но не обязательно (и обычно это не так).