Является ли лямбда-выражение чем-то большим, чем анонимный внутренний класс с одним методом?

40

В Java 8 появилась новая реклама с долгожданными лямбда-выражениями; каждые 3 дня появляется новая статья о том, как они круты.

Насколько я понял, лямбда-выражение - это не что иное, как анонимный внутренний класс с одним методом (по крайней мере, на уровне байт-кода). Помимо этого он имеет еще одну приятную особенность - вывод типа, но я считаю, что эквивалент этого можно достичь с помощью обобщений на некотором уровне (конечно, не таким аккуратным способом, как с лямбда-выражениями).

Зная это, принесут ли лямбда-выражения нечто большее, чем просто синтаксический подсахаринг в Java? Могу ли я создать более мощные и гибкие классы или другие объектно-ориентированные конструкции с лямбда-выражениями, которые невозможно построить с использованием текущих возможностей языка?

m3th0dman
источник
30
Когда язык завершен по Тьюрингу, каждая добавленная функция теоретически может быть описана как «синтаксический сахар».
Филипп
34
@ Филипп: Это неправильно. Определяющей характеристикой синтаксического сахара является то, что десугаринг является чисто локальным и не меняет глобальную структуру программы. Существует множество функций, которые невозможно реализовать с помощью локальных преобразований. Например, если вы возьмете гипотетический язык, идентичный Java, но с надлежащими хвостовыми вызовами, было бы невозможно десугаровать хвостовые вызовы в «чистую» Java без глобального преобразования всей программы.
Йорг Миттаг,
2
@Philipp: К сожалению, есть только один ответ за комментарии, в противном случае я бы проголосовал за комментарий Йерга 1000 раз. Нет, неправильно, что любую функцию можно описать как синтаксический сахар. Синтаксический сахар не добавляет новой семантики. Фактически, AFAIK предложенные Java-лямбды НЕ являются синтаксическим сахаром для специальных анонимных внутренних классов, потому что они имеют различную семантику.
Джорджио
1
@ Джорджио: а какая у них семантика?
user102008
2
@ Джорджио: Да, но преобразование thisв OuterClass.this- это часть процесса удаления лямбда-выражений в анонимном классе.
user102008

Ответы:

47

tl; dr: хотя это в основном синтаксический сахар, этот более приятный синтаксис делает множество практических вещей, которые заканчивались бесконечными, нечитаемыми строками скобок и скобок.

Ну, на самом деле все наоборот, поскольку лямбды намного старше, чем Java. Внутренние анонимные классы с одним методом являются (были) наиболее близкими к лямбдам Java. Это приближение, которое было «достаточно хорошим» в течение некоторого времени, но имеет очень неприятный синтаксис.

На первый взгляд, лямбды Java 8 кажутся не чем иным, как синтаксическим сахаром, но когда вы смотрите под поверхность, вы видите множество интересных абстракций. Например, спецификация JVM трактует лямбду совершенно иначе, чем «истинный» объект, и хотя вы можете обрабатывать их так, как если бы они были объектами, JVM не обязана реализовывать их как таковые.

Но в то время как все эти технические хитрости интересны и актуальны (поскольку они позволяют в будущем оптимизировать JVM!), Реальным преимуществом является «просто» синтаксическая часть сахара.

Что легче читать:

myCollection.map(new Mapper<String,String>() {
  public String map(String input) {
    return new StringBuilder(input).reverse().toString();
  }
});

или:

myCollection.map(element -> new StringBuilder(element).reverse().toString());

или (используя дескриптор метода вместо лямбды):

myCollection.map(String::toUpperCase);

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

Йоахим Зауэр
источник
1
Я полностью согласен с синтаксической частью подсахаривания; мой вопрос был о новых объектно-ориентированных конструкциях, возможных только с лямбда-выражениями; В конце концов, Java - это объектно-ориентированный язык. Подумайте о сравнении с дженериками, и о том, как они приобрели большую гибкость в Java, а гибкость невозможна без них.
m3th0dman
7
@ m3th0dman: на самом деле дженерики не добавили новых способностей. A Listможет содержать любой объект, List<String>может «только» содержать строки. Обобщения добавили ограничение (и возможность формализовать ограничения!), А не добавление к власти. Почти все, что с тех пор, как Java 1.1 стала синтаксическим сахаром. И это не обязательно плохо.
Иоахим Зауэр
3
@ m3th0dman: На самом деле дженерики не добавили новых возможностей, потому что в Java они просто синтаксический сахар. A List<String>- это просто Listприведение к Stringнекоторым возвращаемым значениям. Тем не менее, они чрезвычайно полезны и сделали язык намного более удобным для написания. Лямбды похожи.
Ян Худек
3
На самом деле это гораздо больше, чем просто синтаксический сахар. Большая часть функциональности реализована в JVM как первоклассный гражданин благодаря использованию вызываемого динамического байт-кода + некоторые довольно значительные изменения в системе вывода типов. Это не реализовано как анонимные внутренние классы.
Мартейн Вербург
1
@JanHudec: ну, они немного больше, чем синтаксический сахар, потому что компилятор действительно уважает их. Среда не заботится о них, что это правда.
Иоахим Зауэр
14

Да, для Java это не что иное, как лучший способ создания анонимного внутреннего класса. Это из-за фундаментального решения в Java, что каждый бит байтового кода должен жить в определенном классе, который не может быть изменен сейчас после десятилетий унаследованного кода.

Однако это не то, о чем на самом деле говорят лямбда-выражения. В формализмах, где они являются нативными понятиями, а не искривлением "скачка на поводке", лямбды являются фундаментальными строительными блоками; и их синтаксис, и отношение людей к ним очень разные. Пример анонимной рекурсивной функции, созданной исключительно из лямбд в Структуре и интерпретации компьютерных программ , способен изменить всю вашу концепцию вычислений. (Тем не менее, я совершенно уверен, что способ научиться программировать - это просто эзотерика, чтобы стать историей успеха.)

Килиан Фот
источник
1
Узнать, что все можно реализовать в терминах лямбд, очень поучительно и, на мой взгляд, не «эзотерично». (Тем не менее, я полагаю, что большинство «программистов» не интересуются интересной теорией CS). Это не значит, что лямбды особенные; это просто означает, что лямбды являются одним из типов универсальной конструкции. Есть и другие, как комбинаторы SKI. Лямбды являются фундаментальными строительными блоками в функциональных парадигмах, но, возможно, что-то еще может быть фундаментальным в другой парадигме.
user102008
+1 за «искривление в прыжке»: я часто задаюсь вопросом, почему некоторые языки постоянно меняются, подражая другим популярным языкам, и почему программисты терпят это. Я люблю лямбды и часто их использую в Scala, Lisp, Haskell, но в Java или C ++ они чувствуют себя запоздалой мыслью о языке только потому, что стали популярными в других языках.
Джорджио
2

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

user102008
источник
1
почему понизить? то, что я сказал, совершенно правильно
user102008
То, что вы пишете, звучит разумно для Java-лямбд, но это предложение было отклонено в пользу другого ( doanduyhai.wordpress.com/2012/07/12/… ).
Джорджио
1
@ Джорджио: Я ничего не "предлагаю". Что именно вы не согласны с тем, что я сказал? Да, внутренняя часть выражения будет выглядеть иначе, например, мы добавим метод, и да, thisего нужно будет преобразовать в OuterClass.this. Это не противоречит тому, что я сказал - каждое лямбда- выражение может быть преобразовано в семантически эквивалентное выражение анонимного класса, не изменяя ничего вне этого выражения . Я не говорил, что внутренняя часть не изменится.
user102008
3
То, что вы сказали, неверно. Семантика лямбда- и аноновых внутренних классов не совсем одинакова (хотя они схожи). С вершины моей головы, смысл значения этого изменяется; и только внутренние классы всегда приводят к новому экземпляру объекта, тогда как лямбда может или не может.
Стюарт Маркс
1
@StuartMarks: Нет, вы не читали мой ответ. Я не говорил, что каждый из них может делать все, что делает другой. Я сказал, что у лямбды есть эквивалентный анонимный класс, который семантически одинаков. Как я уже говорил в комментариях, thisлямбда является частью синтаксического сахара и не является разницей в семантике. И о различиях в реализации, реализации! = Семантика. О различиях в идентичности объекта; идентичность объекта чрезвычайно касательна к функциональности лямбд; в любом случае никто не проверяет объектную идентичность классов lambdas / anon, потому что это бесполезно.
user102008
1

Йоахим Зауэр уже хорошо поработал, отвечая на ваш вопрос, но только намекнул на то, что я считаю важным. Поскольку лямбды не являются классами, они также не компилируются как таковые. Все анонимные внутренние классы приводят к созданию файла .class, который, в свою очередь, должен быть загружен ClassLoader. Таким образом, использование Lambdas вместо этого не только делает ваш код более красивым, но также уменьшает размер вашего скомпилированного кода, объем памяти вашего ClassLoader и время, необходимое для передачи битов с вашего жесткого диска.

Кристоф Гриммер-Дитрих
источник
-1

Нет, они не.

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

Гвидо Ансельми
источник
1
Функциональное программирование может быть полностью ортогональным к объектно-ориентированному программированию (см .: Scala и F #). Это похоже на мнение кого-то, кто просто не любит функциональное программирование в принципе.
KChaloux
1
@KChaloux - не функциональный ненавистник программирования. Ответ написан кем-то, кто предпочитает иметь молоток И отвертку, а не молоток. Программирование ОО является хорошей парадигмой, как и функциональное программирование. Я предпочитаю, чтобы язык был понятен относительно его намерений. Лямбда, я чувствую себя грязно, иначе ОО природа Явы. Java не была разработана, чтобы быть функциональным языком. Людям нужна структура, чтобы делать свою лучшую работу.
Гвидо Ансельми