Лямбда-выражение Java 8 и первоклассные значения

Являются ли замыкания в Java 8 действительно первоклассными значениями или это всего лишь синтаксический сахар?


person xdevel2000    schedule 05.03.2013    source источник
comment
этот часто задаваемый вопрос весьма полезен для вопросов по этой теме - lambdafaq.org/are-lambda- объекты-выражения   -  person Matt    schedule 05.03.2013
comment
Они определенно не синтаксический сахар; см. ответ Стюарта Маркса на этот вопрос: программисты. stackexchange.com/questions/177879/   -  person Maurice Naftalin    schedule 05.03.2013
comment
Я не хочу обсуждать эту тему, однако имеется довольно много информации о проекте lambda. страница, описывающая точную реализацию JDK, она также охватывает методы виртуального расширения.   -  person Brett Ryan    schedule 07.03.2013
comment
@MauriceNaftalin - Стюарт Маркс не говорит, что они не синтаксический сахар. Он говорит, что они не являются синтаксическим сахаром для анонимного внутреннего класса. Очень разные.   -  person Graham Lea    schedule 28.07.2014
comment
Ответ @GrahamLea Stuart на этот вопрос, на 1 строку ниже, начинается со слов, которые я бы сказал, что замыкания Java 8 (Lambdas) не являются ни простым синтаксическим сахаром, ни первоклассными значениями. Кроме того, для чего еще они могут быть сахаром, кроме как для Айка?   -  person Maurice Naftalin    schedule 31.07.2014
comment
@MauriceNaftalin Ты прав. Должно быть, я был слеп в тот день. Ваше здоровье.   -  person Graham Lea    schedule 08.08.2014


Ответы (5)


Я бы сказал, что замыкания Java 8 («лямбды») не являются ни простым синтаксическим сахаром, ни первоклассными значениями.

Я рассмотрел проблему синтаксического сахара в ответе на другой вопрос StackExchange.

Что касается того, являются ли лямбды «первоклассными», это действительно зависит от вашего определения, но я приведу аргумент, что лямбды на самом деле не являются первоклассными.

В некотором смысле лямбда хочет быть функцией, но Java 8 не добавляет типы функций. Вместо этого лямбда-выражение преобразуется в экземпляр функционального интерфейса. Это позволило добавить лямбда-выражения в Java 8 с небольшими изменениями в системе типов Java. После преобразования результатом является ссылка, как и ссылка любого другого типа. На самом деле использование Lambda — например, в методе, которому в качестве параметра было передано лямбда-выражение — неотличимо от вызова метода через интерфейс. Метод, который получает параметр типа функционального интерфейса, не может сказать, было ли ему передано лямбда-выражение или экземпляр некоторого класса, реализующего этот функциональный интерфейс.

Дополнительные сведения о том, являются ли лямбда-выражения объектами, см. в ответе на часто задаваемые вопросы о Lambda на этот вопрос.

Учитывая, что лямбды преобразуются в объекты, они наследуют (буквально) все характеристики объектов. В частности, объекты:

  • иметь различные методы, такие как equals, getClass, hashCode, notify, toString и wait
  • иметь идентификационный хэш-код
  • может быть заблокирован блоком synchronized
  • можно сравнить с помощью операторов == и != и instanceof

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

Говоря более кратко, в Java объекты имеют идентичность, но значения (особенно значения функций, если они должны существовать) не должны иметь никакого понятия идентичности. В Java 8 нет типов функций. Вместо этого лямбда-выражения преобразуются в объекты, поэтому у них много багажа, который не имеет отношения к функциям, особенно к идентичности. Мне это не кажется "первоклассным".

Обновление от 24 октября 2013 г.

Я размышлял над этой темой с тех пор, как опубликовал свой ответ несколько месяцев назад. С технической точки зрения все, что я написал выше, верно. Вывод, вероятно, выражается более точно, поскольку лямбда-выражения Java 8 не являются чистыми (в отличие от первоклассных) значениями, потому что они несут с собой много объектного багажа. Однако то, что они нечисты, не означает, что они не первоклассные. Рассмотрим определение Википедии функции первого класса. Вкратце, перечисленные там критерии признания функций первоклассными — это способности:

  • передавать функции в качестве аргументов другим функциям
  • возвращать функции из других функций
  • присваивать функции переменным
  • хранить функции в структурах данных
  • иметь функции быть анонимными

Лямбда-выражения Java 8 соответствуют всем этим критериям. Так что они кажутся первоклассными.

В статье также упоминаются имена функций, не имеющие особого статуса, вместо этого имя функции представляет собой просто переменную, тип которой является типом функции. Лямбда-выражения Java 8 не соответствуют этому последнему критерию. В Java 8 нет типов функций; он имеет функциональные интерфейсы. Они эффективно используются как функциональные типы, но они вовсе не являются функциональными типами. Если у вас есть ссылка, тип которой является функциональным интерфейсом, вы понятия не имеете, является ли она лямбдой, экземпляром анонимного внутреннего класса или экземпляром конкретного класса, который реализует этот интерфейс.

Таким образом, лямбда-выражения в Java 8 являются более первоклассными функциями, чем я думал изначально. Они просто не являются чистыми первоклассными функциями.

person Stuart Marks    schedule 06.03.2013
comment
Очень хороший ответ, спасибо. Однако у меня все еще есть некоторые сомнения относительно значения синтаксического сахара. Например, в Java расширенный цикл for преобразуется компилятором в обычный цикл for (следовательно, это синтаксический сахар), и поэтому, если лямбда-выражение преобразуется в функциональный интерфейс, почему бы не рассматривать это преобразование как синтаксический сахар? - person xdevel2000; 06.03.2013
comment
Я думаю, зависит от вашего определения синтаксического сахара. Для меня сахар — это когда компилятор переписывает для вас что-то, что вы могли бы написать сами. Если бы вы написали лямбду как анонимный внутренний класс, последний действительно имел бы другую семантику: это настоящий подкласс, всегда создает новый экземпляр, может добавлять поля, может добавлять/переопределять методы и т. д. Это не применимо. к лямбдам. - person Stuart Marks; 06.03.2013
comment
Я думаю, что есть некоторое противоречие в том, что вы говорите. Сначала вы говорите: лямбда-выражение преобразуется в экземпляр функционального интерфейса, и для меня сахар — это когда компилятор переписывает для вас что-то, что вы могли бы написать сами, чем вы говорите: ... Это не относится к лямбда-выражениям. Но лямбда преобразуется компилятором в функциональный интерфейс... так что это синтаксический сахар! (Также C# и Scala делают преобразование...). - person xdevel2000; 07.03.2013
comment
@xdevel2000 xdevel2000 Это не синтаксический сахар, потому что вы не могли бы вручную создать функциональный интерфейс, точно такой же, как тот, который компилятор генерирует из Closures. Одна из основных причин этого заключается в том, что байт-код, сгенерированный для замыканий, использует инструкцию (invokedynamic), которая недоступна для какой-либо другой конструкции в Java. - person Cyrille Ka; 09.03.2013
comment
Сирил Ка в основном прав. Однако, чтобы прояснить это, я скажу, что преобразование отличается от перезаписи. Я использую преобразование в смысле преобразования типов. Например, если у нас есть Object o = "Hello";, литерал Hello String преобразуется в ссылку типа Object. С другой стороны, переписывание — это преобразование простого синтаксиса в более сложный синтаксис, который вы могли бы написать сами. Преобразование и переписывание — разные понятия. - person Stuart Marks; 09.03.2013
comment
Можно посмотреть на это под другим углом. Если бы в Java не было чистых значений (как в Scala или JavaScript — 1 — это объект, а (1).toString() — допустимое выражение), лямбды Java 8 можно было бы считать настоящими гражданами первого класса. Упаковка/распаковка несколько устраняет этот недостаток языка. Конечно, equals() и т. д. должны быть определены и предсказуемы во всем — они могут быть во время выпуска. Хотя я согласен с вашим ни-ни в целом, я думаю, что лямбда-выражения Java гораздо ближе к первоклассному, чем к чистому сахару. А в будущем (Java 9?) они станут еще более «естественными». - person Alex Pakka; 24.10.2013
comment
Честные баллы. Понятие первого класса довольно субъективно. Упаковка/распаковка помогает создать иллюзию типов значений, но, к сожалению, лежащая в основе дихотомия примитив/ссылка проступает в неурочное время. Рассмотрим, например, сравнение двух целых чисел в штучной упаковке, используя <= и ==. Но, в целом, в последнее время я также больше думал об этой теме и вскоре обновлю свой ответ. - person Stuart Marks; 25.10.2013
comment
@CyrilleKa: Байт-код - это проблема уровня выполнения/реализации. С чисто языковой точки зрения любое лямбда-выражение можно переписать в выражение создания анонимного класса и получить тот же эффект. - person newacct; 25.10.2013
comment
@newacct Это для другого определения того же эффекта. Я тоже не считаю switch синтаксическим сахаром для группы if, хотя любое switch можно преобразовать в несколько if. Или рассмотрим ООП: любой класс с методами экземпляра можно переписать в структуроподобный класс со статическими методами, принимающими экземпляр в качестве первого параметра. Это не означает, что ООП — это просто синтаксический сахар: внутренне он очень отличается, и это проявляется в отражении. В любом случае, это вопрос семантики. Мы можем не согласиться с определением синтаксического сахара. Я чувствую, что ваш слишком широк, чтобы быть полезным. - person Cyrille Ka; 25.10.2013
comment
Как вы назначаете функцию переменной в Java 8? - person sproketboy; 21.02.2014
comment
Объявите значение типа функционального интерфейса, включая дженерики. Например, Function<String,String> fn = s -> s.toUpperCase(); - person Stuart Marks; 21.02.2014
comment
учитывая существующий метод (не функцию), например class C { static int f() { return 1; }, могу ли я передать C.f насмешнику или что-то в этом роде? Есть ли способ сделать это? - person sam boosalis; 28.05.2014
comment
@samboosalis Я не совсем уверен, что нужно сделать для передачи функции мокеру, но вы можете сделать что-то подобное, используя ссылки на методы. Поскольку ваш существующий метод не принимает параметров и возвращает целое число, он соответствует функциональному интерфейсу IntSupplier. Затем вы можете передать C::f всему, что принимает IntSupplier. На самом деле это не передача ссылки непосредственно на метод f(), это сахар для лямбды () -> C.f(), которая вызывает метод f(). Но это должно быть достаточно близко для большинства целей. - person Stuart Marks; 29.05.2014
comment
вот чего я боялся. я думаю, моя точка зрения заключается в том, что методы по-прежнему являются второсортными, в то время как существуют функциональные интерфейсы, которые действуют как функции первого класса. я просто хотел привести общий пример использования: юнит-тест, который имитирует метод. если бы методы были просто полями, значениями которых являются функции, вы могли бы поменять их местами с чем угодно, имеющим тот же тип. вы говорите, что существует синтаксис C::f для создания функционального интерфейса из метода, и это круто. но я все еще не могу издеваться над методами так же легко, как в python. Спасибо! - person sam boosalis; 29.05.2014
comment
Да, методы остаются методами. Если вы хотите издеваться над одним из них, вам нужно сделать что-то вроде создания динамического прокси-сервера, что, я думаю, делают имитирующие фреймворки. - person Stuart Marks; 29.05.2014

Да, это первоклассные ценности (или будут таковыми после выхода Java 8...)

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

См. также более подробное определение того, что означает «первый класс» в этом контексте:

person mikera    schedule 05.03.2013

На мой взгляд, это синтаксический сахар, но вдобавок к выводу типа, новому пакету java.util.functions и семантике внутренних классов он выступает как первоклассное значение.

person kan    schedule 05.03.2013

Настоящее замыкание с привязкой переменной к внешнему контексту имеет некоторые накладные расходы. Я бы посчитал реализацию Java 8 оптимальной, достаточно чистой.

По крайней мере, это не просто синтаксический сахар.

И я бы не знал более оптимальной реализации.

person Joop Eggen    schedule 05.03.2013

Для меня Lambdas в Java 8 — это просто синтаксический сахар, потому что вы не можете использовать его как гражданин первого класса (http://en.wikipedia.org/wiki/First-class_function) каждая функция должна быть заключена в объект, это накладывает много ограничений по сравнению с языком с чисто первоклассными функциями, такими как SCALA. Замыкания Java 8 могут захватывать только неизменяемые («фактически окончательные») нелокальные переменные.

Вот лучшее объяснение, почему это синтаксический сахар Java Lambdas and Closures

person Roman Nazarevych    schedule 23.09.2014