Глубокое понимание выражений Java Lambda, анонимных функций, замыканий

предисловие

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

Что такое лямбда

Давайте будем более серьезными, Google Translate использует Lambda для перевода:

Ну ничего? Ни в коем случае, поиск в энциклопедии Baidu:

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

Что такое лямбда-выражения

Найдите выражения Lamdba, чтобы увидеть:

Есть картинки и тексты, а также видео, что сейчас очень удобно. Объяснение выдержки из лямбда-выражения следующее:

Лямбда-выражение (лямбда-выражение) — анонимная функция . Лямбда-выражение названо на основе лямбда-исчисления в математике , что напрямую соответствует лямбда-абстракции (лямбда-абстракция). Является анонимной функцией, то есть функцией без имени функции . Лямбда-выражения могут представлять замыкания (обратите внимание на отличие от традиционного понимания математики).

Источник: Лямбда-выражения

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

Примечание: λ — одиннадцатая буква греческого алфавита, соответствующая заглавная буква — Λ, а английское название — лямбда.

Что такое лямбда-исчисление

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

λ-исчисление (английский язык: лямбда-исчисление, λ-исчисление) представляет собой набор формальных систем, разработанных на основе математической логики, использующих связывание переменных и правила подстановки для изучения абстрактного определения функций, их применения и рекурсии . Впервые он был опубликован математиком Алонзо Черчем в 1930-х годах. Как широко используемая вычислительная модель, лямбда-исчисление может четко определить, что является вычислимой функцией, и любая вычислимая функция может быть выражена и оценена в этой форме, и она может имитировать вычислительный процесс одной ленточной машины Тьюринга; хотя, таким образом, лямбда- вычисление исчисление делает упор на применение правил преобразования, а не на конкретные машины, которые их реализуют.

Источник: Лямбда-исчисление_Википедия.

Как упоминалось выше, лямбда-исчисление является формальной системой. Что такое формальная система? В логике и математике формальная система состоит из двух частей: формального языка и набора правил вывода или правил преобразования.

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

Как и язык в лингвистике , формальные языки обычно имеют два аспекта: синтаксис и семантику . Раздел математики и информатики, изучающий синтаксис языков, называется формальной теорией языка , которая изучает синтаксис языка, не работая над его семантикой. В теории формального языка формальный язык представляет собой набор определенных строк конечной длины в алфавите . Формальный язык может содержать бесконечное количество строк.

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

Вышеприведенное взято из: китайского перевода серии лямбда-исчислений Good Math/Bad Math.

состав

Лямбда-исчисление состоит из 3-х элементов: переменная ( имя ) , функция (function) и приложение (application) :

имя

синтаксис

пример

объяснять

переменная ( имя )

<имя>

Икс

Переменная с именем «x»

функция

λ<параметры>.<тело>

λx.x

функция с аргументом 'x' и телом 'x'

приложение

<функция><переменная или функция>

(λx.x)а

Вызов функции "λx.x" с аргументом "a"

Греческая буква λ (произносится: лямбда) и точка (.). λ и точка используются для описания (определения) анонимных функций. Функции начинаются с лямбды и переменных, за которыми следует точка, а затем тело функции. λ не имеет особого значения, это просто говорит о том, что функция начинается отсюда. После λ буква перед точкой называется переменной, часть перед точкой называется головой, а выражение после точки называется телом.

Вопрос : Почему λ?

Ответ : шанс. Возможно, сначала Черч нарисовал там верхний символ, например: (ŷ xy) ab. В рукописи он написал (⋀y.xy) ab. Наконец, наборщик изменил его на этот (λy.xy) ab.

Источник: лямбда-исчисление, написанное учеными-когнитивистами для Xiaobai.

Самая основная функция — это тождественная функция: λx.x, что эквивалентно f(x) = x.Первый «x» — это аргумент функции, а второй — тело функции.

Свободные и ограниченные переменные

  • В функции λx.x "x" называется связанной переменной, поскольку она находится и в теле функции, и в качестве параметра.
  • В λx.y "y" называется свободной переменной, потому что она никогда не объявлялась заранее.

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

выражение

Основным понятием лямбда-исчисления является «выражение» («expression») . Выражение может быть просто переменной (имя), функцией (function) или приложением (application).

Выражение лямбда-исчисления определяется следующим образом:

<выражение> := <имя>|<функция>|<приложение> 
<функция> := λ <имя> .<выражение> <приложение> 
:= <выражение><выражение> 

Форма перевода 
<выражение> := <символ-идентификатор >|<функция>|<приложение>        
<функция> := λ<идентификатор> .<выражение> < 
приложение> := <выражение><выражение>

карри

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

В этом нет ничего плохого, потому что функции — это значения. Вы можете написать функцию, которая принимает один параметр, и эта функция возвращает функцию, которая принимает один параметр, так что вы можете написать функцию, которая принимает два параметра — по сути одно и то же. Это называется Currying, названным в честь великого логика Haskell Curry.

Например, мы хотим написать функцию для достижения x + y. Мы больше привыкли писать что-то вроде: лямбда xy плюс xy. Способ написания функции с одним параметром таков: мы пишем функцию только с одним параметром и позволяем ей возвращать другую функцию только с одним параметром. Итак, x + y становится функцией одного аргумента x, которая возвращает другую функцию, добавляющую x к своему аргументу:

лямбда х. ( лямбда у плюс ху )

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

алгоритм

В лямбда-исчислении есть только два реальных закона: альфа и бета . Альфа также известна как «трансформация», а бета также известна как «статус».

Альфа-преобразование

Альфа — это операция переименования; в основном она говорит о том, что имя переменной не имеет значения: учитывая произвольное выражение в лямбда-исчислении, мы можем изменить имя параметра функции, если мы также изменим все свободные ссылки на него в теле функции .

Итак, например, если есть такое выражение:

лямбда х. если (= x 0), то 1 иначе x ^ 2

Мы можем преобразовать x в y, используя альфа-преобразование (записывается альфа [x / y]), поэтому мы имеем:

лямбда у. если (= y 0), то 1 иначе y ^ 2

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

Бета-протокол

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

Бета-версия в основном означает, что если у вас есть приложение-функция, вы можете заменить часть тела функции, связанную с соответствующим идентификатором функции, заменив идентификатор значением параметра. Звучит запутанно, но им легко пользоваться.

Предположим, у нас есть выражение приложения функции: “ (lambda x . x + 1) 3 “. Так называемая бета-спецификация заключается в том, что мы можем реализовать приложение функции, заменив тело функции (то есть «x + 1») и заменив ссылочный параметр «x» значением «3». Таким образом, результатом бета-протокола является « 3 + 1”.

Чуть более сложный пример: (lambda y . (lambda x . x + y)) q。это интересное выражение, потому что результатом применения этого лямбда-выражения является другое лямбда-выражение: то есть это функция, которая создает функцию. В настоящее время бета-протоколу необходимо заменить все ссылочные параметры «y» на идентификатор «q». Итак, результат есть “ lambda x . x + q “.

Источник: Мое любимое лямбда-исчисление — открытие · блог cgnail

Закрытие или полная привязка

Терм закрыт, если у него нет свободных переменных; в противном случае он открыт.

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

Источник: http://www.cs.yale.edu/homes/hudak/CS201S08/lambda.pdf

Идентификатор называется связанным (ограниченным), если он является параметром закрытого лямбда-выражения; идентификатор свободен, если он не связан ни в каком охватывающем контексте.

λx.xy: В этом выражении y свободен, поскольку не является параметром какого-либо замкнутого лямбда-выражения, а x связан (ограничен, поскольку является параметром замкнутого выражения xy определения функции.

λxy.xy: И x, и y связаны (ограничены) в этом выражении, потому что они являются параметрами в определении функции.

λy .(λx.xyz): Во внутреннем исчислении λx.xyz y и z свободны, а x связан (ограничен). В полном выражении x и y связаны (ограничены): x связан (ограничен) внутренним исчислением, а y связан (ограничен) остальным исчислением. Но z все еще свободен.

При вычислении выражения лямбда-исчисления вы не можете ссылаться на несвязанные (ограниченные) идентификаторы (переменные), потому что невозможно узнать значение свободных переменных, если нет другого способа предоставить значение этих свободных переменных. Однако, когда мы игнорируем контекст и фокусируемся на подвыражениях сложного выражения, свободные переменные допускаются — и очень важно выяснить, какие переменные в подвыражении свободны.

оценивать

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

При вычислении выражения (λx.x)a мы заменяем все вхождения «x» в теле функции на «a».

  • (λx.x)a оценивается как: a
  • (λx.y)a оценивается как: y

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

  • (λx.(λy.x))a оценивается как: λy.a

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

  • (λx.λy.λz.xyz) эквивалентно f(x, y, z) = ((xy) z)

Иногда λxy.<тело> используется взаимозаменяемо с: λx.λy.<тело>

Источник: Изучите X за Y минут: живописные туры по языку программирования.

Что такое анонимная функция

Что такое анонимная функция? Это совершенно точно, анонимная функция — это функция без имени. Насчет того, что такое функция, автор не будет многословен. Обратите внимание, что здесь автор обсуждает анонимные функции в языке программирования Java.

Что вам напоминают анонимные функции ? Анонимный класс , да? Анонимный класс — это класс без имени . Анонимная функция — это функция, не имеющая имени и не принадлежащая какому-либо классу.

Java всегда была объектно-ориентированным языком программирования. Это означает, что все в программировании на Java вращается вокруг объектов (за исключением некоторых примитивных типов для простоты). До Java 8 функция была частью класса, нам нужно использовать класс/объект для вызова функции. В Java 8 и более поздних версиях есть функция, не принадлежащая ни к какому классу.Вам точно будет любопытно.Не нарушает ли это идею ООП? Если мы посмотрим на другие языки программирования, такие как C++, C#, JavaScript, мы обнаружим, что они называются функциональными языками программирования, потому что мы можем писать функции и использовать их, когда нам нужно. Некоторые из этих языков поддерживают объектно-ориентированное программирование, а также функциональное программирование . Что такое функциональное программирование? Чтобы узнать об этом , рекомендуется прочитать статью «Предварительное изучение функционального программирования» Руана Ифэна .

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

Выражения анонимного класса включают следующее:

  • новый оператор;
  • имя интерфейса для реализации или класса для расширения;
  • Круглые скобки, заключающие параметры конструктора, как обычные выражения создания экземпляра класса. Примечание. Если при реализации интерфейса нет конструктора, требуется пара пустых скобок.
  • Тело, которое является телом объявления класса. В частности, в теле разрешены объявления методов, но не операторы.

Поскольку определение анонимного класса является выражением, оно должно быть частью инструкции. Этот анонимный класс объясняет, почему после закрывающей скобки стоит точка с запятой.

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

  • Анонимный класс может получить доступ к членам окружающего его класса.
  • Анонимный класс не может получить доступ к локальным переменным в своей области видимости, которые не объявлены окончательными или эффективными окончательными ( Effectively final ).
  • Как и в случае с вложенными классами, объявления типов (таких как переменные) в анонимных классах скрывают любые другие объявления с тем же именем во внешней области видимости.

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

Анонимные классы также имеют те же ограничения для своих членов, что и локальные классы:

  • Вы не можете объявлять статические блоки инициализации или интерфейсы-члены в анонимном классе.
  • Анонимные классы могут иметь статические члены, если они являются постоянными переменными.

В анонимном классе можно объявить следующее:

  • поле
  • Дополнительные методы (даже если они не реализуют никаких методов супертипа)
  • Инициализаторы экземпляра
  • местный класс (местный класс)

Однако конструкторы не могут быть объявлены в анонимных классах.

Зачем тратить так много времени на введение анонимных классов, например:

общедоступный класс HelloTest { 

    private String text = "world"; 

    открытый интерфейс IHello { 

        void sayHello(); 
        
    } 

    private void hello() { 

        IHello hello = new IHello() { 

            @Override 
            public void sayHello() { 
                System.out.print("Hello" + text); 
            } 
            
        }; 

    } 

    public static void main(String[] args) { 

    } 
}

Приведенный выше код определяет интерфейс IHello, который содержит только один абстрактный метод void sayHello(), написанный с использованием анонимного класса, и компилятор предложит вам заменить его лямбда-выражением.

После замены становится:

общедоступный класс HelloTest { 

    private String text = "world"; 

    открытый интерфейс IHello { 

        void sayHello(); 

    } 

    private void hello() { 

        IHello hello = () -> System.out.print("Hello" + text); 

    } 

    public static void main(String[] args) { 

    } 
}

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

общедоступный класс HelloTest { 

    private String text = "world"; 

    открытый интерфейс IHello { 

        void sayHello(); 

        недействительным время печати(); 
    } 

    private void hello() { 

        IHello hello = new IHello() { 

            @Override 
            public void sayHello() { 
                System.out.print("Hello" + text); 
            } 

            @Override 
            public void printTime() { 

            } 
        }; 

    } 

    public static void main(String[] args) { 

    } 
}

Потому что Java предусматривает, что если интерфейс имеет один и только один абстрактный метод, то интерфейс называется функциональным интерфейсом/функциональным интерфейсом (Functional interface) , например Comparable, Runnable, EventListener, Comparator и т.д. В Java8 и более поздних версиях интерфейсы функций могут использовать лямбда-выражения вместо выражений анонимных классов, что означает, что функциональные интерфейсы могут быть реализованы с использованием анонимных функций вместо анонимных классов , что является одной из характеристик лямбда-выражений. Таким образом, понимание анонимных классов помогает нам понять суть анонимных функций.

Но почему мы называем такой интерфейс функциональным интерфейсом ? Почему бы не назвать это интерфейсом единого метода?

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

Простой пример:

Runnable runnable = new Runnable(){ 
    @Override 
    public void run(){ 
        System.out.println("Работа без Lambda"); 
    } 
}; 
новый поток (выполняемый).start(); 

// Запуск с Lambda 
new Thread(() -> System.out.println("Запуск без Lambda")).start();

Присмотревшись, мы используем эти интерфейсы для передачи кода конструктору Thread, а для класса Thread важным является код в методе run. Кроме того, мы можем легко заменить реализацию метода run, эти интерфейсы на самом деле являются интерфейсами стратегии, потому что это реализация шаблона стратегии, где код, из которого состоит стратегия, внедряется в код, запускающий стратегию во время выполнения.

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

Что делать, если в интерфейсе объявлено несколько абстрактных методов? Можно ли его еще использовать в таком виде? Ответ - нет. Поскольку вам необходимо реализовать все абстрактные методы, лямбда-выражение в этот момент сообщит об ошибке. Чтобы предотвратить объявление нескольких абстрактных методов в функциональном интерфейсе, Java 8 предоставляет аннотацию @FunctionalInterface для объявления функционального интерфейса, ее использование не обязательно, но рекомендуется использовать ее с функциональным интерфейсом, чтобы избежать случайного добавление другого метода. Пример кода выглядит следующим образом.

/** 
* Если интерфейс имеет один и только один абстрактный метод, этот интерфейс называется функциональным интерфейсом/функциональным интерфейсом (Functional interface) 
* В Java8 и более поздних версиях функциональные интерфейсы могут использовать выражения Lambda вместо выражений анонимного класса Другими словами функциональные интерфейсы могут быть реализованы с использованием анонимных функций вместо анонимных внутренних классов. 
*/ 
Открытый интерфейс 
@FunctionalInterface IHelloInner { 
    void sayHello(); 
}

Используйте аннотацию @FunctionalInterface для оформления перед интерфейсом, при попытке добавить абстрактный метод возникнет ошибка компиляции:

Целевой тип этого выражения должен быть функциональным интерфейсом.

Однако функциональные интерфейсы могут добавлять методы по умолчанию и статические методы.

/** 
 * Если интерфейс имеет один и только один абстрактный метод, этот интерфейс называется функциональным интерфейсом/функциональным интерфейсом (Functional interface) 
 * В Java8 и более поздних версиях функциональные интерфейсы могут использовать выражения Lambda вместо выражений анонимного класса Другими словами функциональные интерфейсы могут быть реализованы с использованием анонимных функций вместо анонимных внутренних классов. 
 * Можно добавить любое количество методов по умолчанию и статических методов 
 */ 
@FunctionalInterface 
public interface IHelloInner { 

    /** 
     * Функциональный интерфейс/функциональный интерфейс могут добавлять статические методы 
     */ 
    static void staticFunction() { 

    } 

    static void staticFunction2() { 

    } 

    / * * 
     * Интерфейс функции/интерфейс функции может использовать метод по умолчанию 
     */ 
    default void defaultFunction() { 

    } 

        default void defaultFunction2() { 

        } 

        /** 
         * абстрактный метод 
         */ 
        void sayHello();

    }

Подумайте об этом, почему нефункциональные интерфейсы не могут поддерживать лямбда-выражения?

Примечание. В Java существует два типа внутренних классов: локальные классы (локальные классы) и анонимные классы. Следовательно, анонимный класс должен быть внутренним классом, поэтому термин анонимный внутренний класс относится к классу, который является анонимным внутренним классом . Следовательно, анонимные классы и анонимные внутренние классы означают одно и то же, не беспокойтесь об этом. Локальный класс — это внутренний класс, определенный в теле метода.Если локальный класс не имеет имени, он также является анонимным классом. Но, в свою очередь, анонимный класс не обязательно является локальным классом, потому что он не обязательно находится в теле метода.

Существует два специальных вида внутренних классов: локальные классы и анонимные классы .

Источник: https://docs.oracle.com/javase/tutorial/java/javaOO/nested.html .

Есть два дополнительных типа внутренних классов. Вы можете объявить внутренний класс внутри тела метода. Эти классы известны как локальные классы . Вы также можете объявить внутренний класс в теле метода, не называя класс. Эти классы известны как анонимные классы .

Источник: https://docs.oracle.com/javase/tutorial/java/javaOO/innerclasses.html .

Функциональные интерфейсы — одна из самых важных концепций Java 8, поддерживающая лямбда-выражения, но многие разработчики прилагают много усилий для ее понимания, не понимая сначала, что функциональные интерфейсы делают в Java 8, и тратят время на изучение формул лямбда-выражений и Stream API. Если вы не знаете, что такое функциональный интерфейс и как Lambdas связаны с ним, вы не сможете использовать мощные функции Java 8, такие как Lambda Expressions и Streams API.

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

private void hello() { 
    //Анонимный внутренний класс 
	IHello hello = new IHello() { 
            @Override 
            public void sayHello() { 
                System.out.print("Hello " + text); 
            } 
            
        }; 
    //Анонимная функция (лямбда-выражение формула) 
    IHello hello = () -> System.out.print("Hello" + текст); 

}

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

Разница между анонимным классом и анонимной функцией

анонимный класс

Анонимные функции (лямбда-выражения)

внутренний класс без имени

функция без названия

Он может реализовать интерфейс, содержащий любой абстрактный метод.

Он может реализовать интерфейс, содержащий один абстрактный метод (функциональный интерфейс).

Он может расширять абстрактные или конкретные классы

Он не может расширять абстрактные или конкретные классы

Анонимные классы могут иметь переменные экземпляра и локальные переменные метода.

Анонимные функции могут иметь только локальные переменные

Анонимные классы могут быть созданы

анонимная функция

Внутри анонимного внутреннего класса «это» всегда относится к текущему объекту анонимного класса, а не к внешнему объекту.

Внутри анонимной функции «это» всегда относится к текущему объекту внешнего класса, объекту OuterClass.

Это лучший вариант, если мы имеем дело с несколькими методами

Это лучший вариант, если мы имеем дело с интерфейсами

Во время компиляции будет создан отдельный файл .class.

Во время компиляции отдельные файлы .class не создаются. просто используйте его как частный метод внешнего класса

Выделение памяти по требованию всякий раз, когда мы создаем объект

Он находится в постоянной памяти JVM.

Давайте посмотрим на скомпилированный байт-код анонимных классов и анонимных функций:

общедоступный класс HelloTest { 

    private String text = "world"; 

    открытый интерфейс IHello { 

        void sayHello(); 

    } 

    private void hello() { 

        IHello helloAnonymousClass = new IHello() { 

            @Override 
            public void sayHello() { 
                System.out.print("Hello helloAnonymousClass" + text); 
            } 

        }; 

        IHello helloAnonymousFunction = () -> System.out.print("Hello helloAnonymousFunction " + text); 


    } 

    public static void main(String[] args) { 

    } 
}

Скомпилированный байт-код:

// версия класса 52.0 (52) 
// флаги доступа 0x21 
public class com/nxg/app/HelloTest { 

  // флаги доступа 0x609 
  public static abstract INNERCLASS com/nxg/app/HelloTest$IHello com/nxg/app/HelloTest IHello 
  / / флаги доступа 0x0 
  INNERCLASS com/nxg/app/HelloTest$1 null null 
  // флаги доступа 0x19 
  public final static INNERCLASS java/lang/invoke/MethodHandles$Lookup java/lang/invoke/MethodHandles Lookup 

  // флаги доступа 0x2 
  private Ljava/lang /Нить; text 

  // флаги доступа 0x1 
  public <init>()V 
    ALOAD 0 
    INVOKESPECIAL java/lang/Object.<init> ()V 
    ALOAD 0 
    LDC "world"
    PUTFIELD com/nxg/app/HelloTest.text : Ljava/lang/String; 
    RETURN 
    MAXSTACK = 2 
    MAXLOCALS = 1 

  // флаги доступа 0x2 
  private hello()V 
    NEW com/nxg/app/HelloTest$1 
    DUP 
    ALOAD 0 
    INVOKESPECIAL com/nxg/app/HelloTest$1.<init> (Lcom/nxg/app/HelloTest ;)V 
    ASTORE 1 
    ALOAD 0 
    INVOKEDYNAMIC sayHello(Lcom/nxg/app/HelloTest;)Lcom/nxg/app/HelloTest$IHello; [ 
      // обрабатывать вид 0x6 : INVOKESTATIC
      java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle; Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; 
      // аргументы: 
      ()V,
      // тип обработки 0x7 : INVOKESPECIAL 
      com/nxg/app/HelloTest.lambda$hello$0()V, 
      ()V 
    ] 
    ASTORE 2 
    RETURN 
    MAXSTACK = 3 
    MAXLOCALS = 3 

  // флаги доступа 0x9 
  public static main([Ljava/lang/ String;)V 
    RETURN 
    MAXSTACK = 0 
    MAXLOCALS = 1 

  // флаги доступа 0x1002 
  private synthetic lambda$hello$0()V 
    GETSTATIC java/lang/System.out : Ljava/io/PrintStream; 
    NEW java/lang/StringBuilder 
    DUP 
    INVOKESPECIAL java/lang/StringBuilder.<init> ()V 
    LDC "Hello helloAnonymousFunction" 
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder;
    ALOAD 0 
    GETFIELD com/nxg/app/HelloTest.text : Ljava/lang/String; 
    INVOKEVIRTUAL java/lang/StringBuilder.append (Ljava/lang/String;)Ljava/lang/StringBuilder; 
    INVOKEVIRTUAL java/lang/StringBuilder.toString ()Ljava/lang/String; 
    INVOKEVIRTUAL java/io/PrintStream.print (Ljava/lang/String;)V 
    RETURN 
    MAXSTACK = 3 
    MAXLOCALS = 1 

  // флаги доступа 0x1008 
  статический синтетический доступ$000(Lcom/nxg/app/HelloTest;)Ljava/lang/String; 
    ЗАГРУЗИТЬ 0
    GETFIELD com/nxg/app/HelloTest.text: Ljava/lang/String; 
    ARETURN 
    MAXSTACK = 1 
    MAXLOCALS = 1 
}

В функции привет используйте анонимный класс и, наконец, создайте объект анонимного класса.

NEW com/nxg/app/HelloTest$1 
DUP 
ALOAD 0 
INVOKESPECIAL com/nxg/app/HelloTest$1.<init> (Lcom/nxg/app/HelloTest;)V 
ASTORE 1

При использовании лямбда-выражений генерируется функция с именем lambda$hello$0(), а анонимная функция по сути является функцией.

INVOKEDYNAMIC sayHello(Lcom/nxg/app/HelloTest;)Lcom/nxg/app/HelloTest$IHello; [ 
    // тип обработки 0x6 : INVOKESTATIC 
    java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType; Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite; 
    // аргументы: 
    ()V, 
    // тип обработки 0x7 : INVOKESPECIAL 
    com/nxg/app/HelloTest.lambda$hello$0()V, 
    ()V 
] 
ASTORE 2

Анонимные классы соответствуют классам и объектам, а анонимные функции соответствуют функциям Существенная разница между ними состоит как минимум в разнице между классами, объектами и функциями.

Что такое закрытие

Действительно трудно получить точный, авторитетный и надежный ответ.

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

Понятие замыкания впервые появилось в «Компьютерном журнале» в 1964 году. П. Дж. Ландин предложил понятия аппликативного выражения и замыкания в статье «Механическая оценка выражений».

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

Приведенное выше содержание взято из колонки переобучения переднего плана Ченг Шаофей (зима).

Примечание: согласно комбинации Cheng Shaofei (зима) в сочетании с другой информацией, собранной автором. Можно определить, что концепция замыкания в языках программирования отличается от концепции в области математики. Подробнее см. в этой статье «[Замыкания] Действительно ли вы понимаете замыкания и лямбда-выражения», а также вопрос и ответ о stackoverflow. В чем разница между «замыканием» и «лямбда»?

Есть много разных людей, которые определили замыкания, и вот списки для всех:

  • это функция , которая ссылается на свободную переменную . Эта функция обычно определяется в другой внешней функции и ссылается на переменную во внешней функции. -- < < википедия > >
  • является вызываемым объектом , который записывает некоторую информацию из области , в которой он был создан . -- <<Идеи программирования на Java>>
  • Является анонимным блоком кода , который может принимать параметры и возвращать возвращаемое значение, а также может ссылаться и использовать переменные, определенные в видимой области вокруг него . -- Groovy ['ɡru:vi]
  • это выражение, которое имеет свободные переменные и контекст, в котором эти переменные связаны .
  • Замыкания позволяют вам инкапсулировать некоторое поведение , передавать его как объект , и он по-прежнему имеет доступ к исходному контексту , когда он был впервые объявлен .
  • Выражение (обычно функция) , которое имеет переменные и окружение, привязанное к этим переменным , так что эти переменные также являются частью выражения.
  • Замыкания — это блоки кода , которые могут содержать свободные (несвязанные) переменные ; эти переменные определены не в этом блоке кода или каком-либо глобальном контексте, а в среде, в которой определен блок кода.

Вышеприведенный контент взят из:

Закрытие JAVA - chenjunbiao - 博客园

С таким количеством определений действительно справедливо сказать, что публика права, и свекровь права, и это слишком абстрактно, чтобы понять! Однако в каждом из этих определений есть ключевые слова: переменная, функция, контекст и т. д. Замыкания имеют важное применение в функциях обратного вызова, функциональном программировании и лямбда-выражениях, а замыкания существуют во многих популярных языках, таких как JavaScript, C++ и C#. Чтобы не повторять чужие мнения, придерживаясь концепции, что официальное есть справедливость, давайте взглянем на этапный документ JDK8 (JDK8, выпущенный Oracle 18.03.2014, добавил поддержку лямбда-выражений):

Мы сосредоточимся на новых функциях статьи 126: 126 лямбда-выражений и виртуальных методов расширения , нажмите на ссылку, чтобы увидеть:

подведем итог

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

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

В поисках другого контента в документе я нашел: Closures for the Java Programming Language (BGGA) , поэтому щелкните и посмотрите.

видеть это? В языке программирования Java следующая вещь — это замыкание, так что теперь оно не абстрактно (Xiemeixiaogoutou.jpg).

Шучу, не воспринимайте это всерьез! Обратите внимание, что есть ссылка: Спецификация замыканий BGGA: Замыкания для языка программирования Java (v0.5) , выдержка выглядит следующим образом:

Замыкания для языка программирования Java (v0.5)

Гилад Брача, Нил Гафтер, Джеймс Гослинг, Питер фон дер Ахе

Современные языки программирования предоставляют набор примитивов для составления программ. В частности, Scheme, Smaltalk, Ruby и Scala имеют прямую языковую поддержку для параметризованных блоков кода с отложенным выполнением, которые по-разному называются лямбда -выражениями , анонимными функциями или замыканиями .. Они обеспечивают естественный способ выражения некоторых видов абстракций, которые в настоящее время довольно неудобно выражать в Java. Для программирования в небольших масштабах анонимные функции позволяют абстрагировать алгоритм от фрагмента кода; то есть они позволяют легче извлекать общие части двух почти идентичных фрагментов кода. Для программирования в больших масштабах анонимные функции поддерживают API, которые выражают алгоритм, абстрагированный от некоторых вычислительных аспектов алгоритма. Например, они позволяют писать методы, действующие как определяемые библиотекой управляющие конструкции .

Замыкания для языка программирования Java (v0.5)

Джерард Брача, Нил Гарфт, Джеймс Гослинг, Питер фон дер Ахер

Современные языки программирования предоставляют смешанные примитивы для написания программ. В частности, Scheme, Smalltalk, Ruby и Scala имеют прямую языковую поддержку для параметризованных блоков кода отложенного выполнения, известных как лямбда-выражения , анонимные функции или замыкания , которые обеспечивают естественный способ выражения определенных абстракций, которые в настоящее время трудно выразить в Java. Программирование с помощью небольших анонимных функций позволяет абстрагировать алгоритм над фрагментом кода, то есть они позволяют легче извлекать общие части двух почти идентичных фрагментов кода. Для крупномасштабного программирования анонимные функции поддерживают API-интерфейсы, которые выражают алгоритмы, абстрагирующие некоторые вычислительные аспекты алгоритма. Например, они позволяют писать методы, действующие как управляющие структуры, определенные библиотекой .

Ну может вам нужен вывод (заверение), на языке программирования Java:

Лямбда-выражения — это анонимные функции и замыкания

Выглядит это так, это блок кода (блоки кода) :

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

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

Вы заметили? Автор не использовал знак равенства (=) для обозначения взаимосвязи между лямбда-выражениями, анонимными функциями ( Anonymous Functions ) и замыканиями (Closures) , а использовал «да» , почему? Потому что у них разные функции (угол мышления).

Поясню на возможно неуместном примере:

Вы программист, любовник своей жены и отец своего ребенка.

Здесь некорректно использовать выражение программист = любовник = отец, потому что программисты, любовники и отцы — это не одно и то же, хотя они и являются одной из ваших идентичностей, но выражены с разных сторон (

Это похоже на очень интересное объяснение: разница между абстрактным классом и интерфейсом, что представляет абстрактный класс и что представляет интерфейс, может сделать.

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

Понятие закрытия четко не определено, и у каждого может быть свое понимание в сердце. Если посмотреть только на семантику самого замыкания, кажется, что оно имеет значение замыкания и замыкания, а соответствующее английское слово closures также означает закрытие. Тогда мы должны думать о том, что закрыто (окружено)? Почему закрыть (окружить)?

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

  • Условие 1: должно соответствовать синтаксису лямбда-выражения.
  • Условие 2: Должна быть анонимной функцией
  • Условие 3: Должна быть реализация функционального интерфейса
  • Условие 3: Должна быть возможность ссылаться на переменные-члены внешнего класса и локальные переменные в теле функции, где они расположены.
  • Условие 4: Должна быть возможность определить тип параметра и тип возвращаемого значения в соответствии с контекстом (средой).

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

Что именно закрыто (закрыто)?

Замыкание закрывает (окружает) внешние переменные, на которые есть ссылки в лямбда-выражении (то есть переменные, не объявленные самим лямбда-выражением, или называемые свободными переменными).

Почему закрыть (окружить)?

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

Так что же такое замыкания? Замыкания - это анонимные функции , связанные с контекстом (среда выполнения) с использованием синтаксиса лямбда-выражения? Автор не уверен, просто посмотрите, главное иметь собственное мышление.

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

Лямбда-выражение

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

В Java 8 добавлено много новых функций, которые могут быть затронуты в этой статье, в основном следующие:

  • Лямбда-выражения — они позволяют вам рассматривать функциональность как параметры метода или код как данные. Лямбда-выражения позволяют более компактно выражать экземпляры интерфейсов с одним методом (называемых функциональными интерфейсами).
  • Справочник по методу — Справочник по методу предоставляет очень полезный синтаксис для прямой ссылки на метод или конструктор существующего класса или объекта (экземпляра) Java. В сочетании с лямбда-выражением ссылка на метод может сделать структуру языка более компактной и лаконичной, уменьшив количество избыточного кода.
  • Метод по умолчанию — метод по умолчанию — это метод, который имеет реализацию в интерфейсе.
  • Новые инструменты — новые инструменты компиляции, такие как: движок Nashorn jjs, анализатор зависимостей классов jdeps.
  • Stream API — недавно добавленный Stream API (java.util.stream) привносит в Java настоящий стиль функционального программирования.
  • Date Time API — улучшенная обработка дат и времени.
  • Необязательный класс — необязательный класс был частью библиотеки классов Java 8 для обработки исключений нулевого указателя.
  • Nashorn, JavaScript Engine — Java 8 предоставляет новый javascript-движок Nashorn, который позволяет нам запускать определенные приложения javascript на JVM.

Дополнительные новые функции см. на официальном сайте: Что нового в JDK 8

Лямбда-выражения. что напоминает тебе Регулярные выражения, да. Регулярные выражения обычно используются для извлечения и замены текста, соответствующего определенному шаблону (правилу). Какова роль лямбда-выражения ? Ранее уже были ответы, которые используются для представления замыканий . Видите ли, лямбда-выражение по сути является выражением.

В Java лямбда-выражение — это выражение, представляющее экземпляр функционального интерфейса ( Functional interface ) .

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

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

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

Первый взгляд на лямбда-выражения

Как говорится: вы никогда не ели свинину и не видели, как бегает свинья? Если это мул или лошадь, которая выйдет на прогулку, вы узнаете, верно? Взгляните на код ниже:

Без использования лямбда-выражений:

button.addActionListener(new ActionListener(){ 
    public void actionPerformed(ActionEvent actionEvent){ 
        System.out.println("Обнаружено действие"); 
    } 
});

Используйте лямбда-выражения:

button.addActionListener( actionEvent -> { 
    System.out.println("Обнаружено действие"); 
});

Более очевидный пример.

Без использования лямбда-выражений:

Runnable runnable = new Runnable(){ 
    @Override 
    public void run(){ 
        System.out.println("Работа без Lambda"); 
    } 
};

Используйте лямбда-выражения:

Runnable runnable = ()->System.out.println("Запуск из Lambda");

Видно, что анонимные внутренние классы заменены лямбда-выражениями, для сравнения установлено, что после использования лямбда-выражений не только упрощается код, но и значительно уменьшается объем кода, но улучшается читабельность. Автор Отнеситесь к этому с долей скептицизма (хотя, по крайней мере, иерархия кода приятнее).

В первом примере лямба-выражение передается в метод addActionListener в качестве параметра, во втором лямба-выражение используется как функция (выражение). Итак, как вы думаете, улучшится ли читабельность кода после использования лямбда-выражений?

На самом деле, если вы используете анонимный класс выше, продвинутая IDE обязательно предложит вам заменить лямбда-выражение, подумайте, почему? Посмотрим, как предложит IDE:

Анонимный новый IHello() можно заменить лямбдой

Информация об инспекции: сообщает обо всех анонимных классах, которые можно заменить лямбда-выражениями.

Обратите внимание, что если анонимный класс преобразуется в лямбда-выражение без сохранения состояния, тот же лямбда-объект может повторно использоваться средой выполнения Java во время последующих вызовов. С другой стороны, при использовании анонимного класса каждый раз создаются отдельные объекты. Таким образом, применение быстрого исправления может привести к изменению семантики в редких случаях, например, когда в качестве ключей HashMap используются экземпляры анонимных классов.

Синтаксис Lambda не поддерживается в Java 1.7 или более ранних JVM.

Анонимный новый IHello() можно заменить лямбдой

Inspection Info: сообщает обо всех анонимных классах, которые можно заменить лямбда-выражениями.

Обратите внимание, что если вы преобразуете анонимный класс в лямбда-выражение без сохранения состояния, среда выполнения Java может повторно использовать один и тот же лямбда-объект во время последующих вызовов. С другой стороны, при использовании анонимных классов каждый раз создаются отдельные объекты. Поэтому в редких случаях применение быстрого исправления может привести к изменению семантики, например, когда в качестве ключей HashMap используются экземпляры анонимных классов.

Java 1.7 или более ранние JVM не поддерживают синтаксис Lambda.

Измените проверку кода HelloTest до:

private void hello() { 

    IHello helloAnonymousClass = new IHello() { 

        @Override 
        public void sayHello() { 
            System.out.print(this + "Hello helloAnonymousClass" + text + "\n"); 
        } 

    }; 

    IHello helloAnonymousFunction = () -> { 
        System.out.print(this + "Hello helloAnonymousFunction" + text + "\n"); 
    }; 

    helloAnonymousClass.sayHello(); 
    helloAnonymousFunction.sayHello(); 


}

Результат операции следующий:

com.nxg.app.hellotest$1@34ce8af7 hello helloanonymousclass world 
com.nxg.app.hellotest@b684286 
helloanonymousfunction world 
com.nxg.app.hellotest$1@880ec60 helloanonyfus

Можно обнаружить, что каждый раз, когда вызывается функция sayHello, helloAnonymousClass вызывается другим экземпляром объекта анонимного класса HelloTest$1. Функция helloAnonymousFunction вызывается внешним (закрытым) классом. Основная причина указана выше при анализе скомпилированного байт-кода HelloTest, поэтому я не буду здесь вдаваться в подробности.

Синтаксис лямбда-выражения

Лямбда-выражения очень похожи на объявления функций, на самом деле лямбда-выражения — это анонимные функции без имени.

Типичный синтаксис лямбда-выражения выглядит следующим образом:

(x, y) -> x + y //Эта функция принимает два параметра и возвращает их сумму.

Как видите, типы параметров x и y не объявлены, поэтому это лямбда-выражение можно использовать в нескольких местах, а параметры могут соответствовать int, Integer или простой String. В зависимости от контекста он либо добавит два целых числа, либо объединит две строки.

например:

Интерфейс @FunctionalInterface 
Operator<T> { 
  T process(T a, T b); 
} 

Operator<Integer> addOperation = (a, b) -> a + b; 
System.out.println(addOperation.process(3, 3)); //Выводит 6 

Operator<String> appendOperation = (a, b) -> a + b; 
System.out.println(appendOperation.process("3", "3")); //Выводит 33 

Operator<Integer> multipleOperation = (a, b) -> a * b; 
System.out.println(multiplyOperation.process(3, 3)); //Выводит 9

Другие синтаксисы для лямбда-выражений:

либо 
 
(параметры) -> выражение //1 
 
, либо 
 
(параметры) -> { операторы; } //2 
 
или 
 
() -> выражение //3

Ниже приведены важные характеристики лямбда-выражений:

  • Необязательное объявление типа: нет необходимости объявлять тип параметра, и компилятор может единообразно идентифицировать значение параметра.
  • Необязательные круглые скобки параметра: Параметр не должен определять круглые скобки, но несколько параметров должны определять круглые скобки.
  • Необязательные фигурные скобки: если тело содержит оператор, фигурные скобки не требуются.
  • Необязательное ключевое слово возврата: если тело имеет только одно возвращаемое значение выражения, компилятор автоматически вернет значение, фигурные скобки должны указать, что выражение возвращает значение.

Пример лямбда-выражения

Простой пример лямбда-выражения:

// 1. Параметры не требуются, возвращаемое значение 5   
() -> 5   
  
// 2. Получить параметр (числовой тип) и вернуть его двойное значение   
x -> 2 * x   
  
// 3. Принять 2 параметра ( Number) и вернуть их разность   
(x, y) -> x – y   
  
// 4. Получить 2 целых числа int, вернуть их сумму   
(int x, int y) -> x + y   
  
// 5. Принять строковый объект и печатает его на консоли, не возвращая никакого значения (выглядит как возврат void)   
(String s) -> System.out.print(s)

Посмотрим, как это работает на практике:

открытый класс Java8Tester { 
    
    
   интерфейс MathOperation { 
      int operation (int a, int b); 
   } 
    
   interface GreetingService { 
      void sayMessage (строковое сообщение); 
   } 
    
   private int operation (int a, int b, MathOperation mathOperation) { 
      return mathOperation.operation (a, b); 
   } 
   
   public static void main(String args[]){ 
      Java8Tester tester = new Java8Tester(); 
        
      // объявление типа 
      MathOperation add = (int a, int b) -> a + b; 
        
      // 
      вычитание MathOperation без объявления типа = (a, b) -> a - b; 
        
      // оператор возврата в фигурных скобках 
      MathOperation multiplication = (int a, int b) -> { return a * b; };
        
      // Без фигурных скобок и оператором return 
      MathOperation Division = (int a, int b) -> a / b; 
        
      System.out.println("10 + 5 = " + tester.operate(10, 5, сложение)); 
      System . out.println("10 - 5 = " + tester.operate(10, 5, вычитание)); 
      System.out.println("10 x 5 = " + tester.operate(10, 5, умножение)); 
      System . out.println("10 / 5 = " + tester.operate(10, 5, Division)); 
        
      // без скобок 
      GreetingService GreetingService1 = message -> 
      System.out.println("Hello " + message); 
        
      // со скобками 
      ПриветствиеServicegreatService2 = (сообщение) -> 
      System.out.println("Привет" + сообщение); 
        
      welcomeService1.sayMessage("Рунооб");
      приветствиеService2.sayMessage("Google"); 
   } 
}

Характеристики лямбда-выражений

Параметры лямбда-выражения могут иметь ноль, один или несколько.

(х, у) -> х + у 
(х, у, г) -> х + у + г

Тело лямбда-выражения может содержать ноль, один или несколько операторов.

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

Если в теле есть несколько операторов, эти операторы должны быть заключены в фигурные скобки.

(параметры) -> { операторы; }

Тип параметра может быть явно объявлен или выведен из контекста. Несколько параметров должны быть заключены в круглые скобки и разделены запятыми. Пустые скобки используются для обозначения пустых параметров.

() -> выражение

При наличии одного параметра круглые скобки не применяются, если его тип можно вывести.

а -> вернуть * а;

В лямбда-выражениях не может быть предложения throws, а тип параметра выводится из контекста, в котором используется параметр, и тела, в котором находится параметр.

Лямбда-выражения не могут быть универсальными, т. е. они не могут объявлять универсальные параметры.

Область видимости переменных для лямбда-выражений

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

Дополнительная литература: Java Final и эффективно Final

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

В Java переменные-члены:

  • Переменные-члены в классе — это поля.
  • Переменные внутри методов или блоков кода — они называются локальными переменными.
  • Переменные в объявлениях методов — они называются параметрами.

Можно считать, что пока переменная не объявлена ​​в лямбда-выражении, она является свободной переменной для лямбда-выражения .

Параметр или локальная переменная с тем же именем, что и у локальной переменной, не допускается в лямбда-выражении.

переменная область видимости

Разница между переменными-членами и локальными переменными:

Переменные-члены:

1. Переменные-члены определены в классе, и к ним можно получить доступ во всем классе.

2. Переменные-члены устанавливаются при создании объекта, исчезают при исчезновении объекта и существуют в куче памяти, где расположен объект.

3. Переменные-члены имеют значения инициализации по умолчанию.

локальная переменная:

1. Локальные переменные определены только в локальной области видимости, например: в функциях, операторах и т. д., и действительны только в той области, к которой они принадлежат.

2. Локальные переменные существуют в памяти стека, и область действия заканчивается, и пространство переменных будет освобождено автоматически.

3. Для локальных переменных нет значения инициализации по умолчанию.

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

местный класс (местный класс)

Анонимный класс (анонимный внутренний класс)

Лямбда-выражения (анонимные функции/замыкания)

Переменные-члены

Это доступно (да)

Можно ли его изменить (не окончательный вариант)

Это доступно (да)

Можно ли его изменить (не окончательный вариант)

Это доступно (да)

Можно ли его изменить (не окончательный вариант)

локальная переменная

Это доступно (да)

Можно ли изменить (нет)

Это доступно (да)

Можно ли изменить (нет)

Это доступно (да)

Можно ли изменить (нет)

Но превратите его в атомарный класс для реализации

Для локальных переменных те, что были до Java 8, должны быть изменены с помощью final для использования локальными классами (локальными классами),

Доступ к анонимному классу (анонимный внутренний класс), лямбда-выражению (анонимная функция/замыкание), в противном случае компиляция и компиляция сообщит об ошибке.

Почему локальные классы и анонимные классы могут ссылаться на переменные-члены внешних классов? Поскольку внутренний класс содержит ссылку на внешний класс, и даже если переменная объявлена ​​как частная, на нее также можно ссылаться. например:

/** 
 * Внешний класс (закрытый класс) 
 */ 
public class OuterClass { 
    //частная переменная-член 
    private String privateText = "privateText"; 
    //частная статическая переменная 
    private static String privateStaticText = "privateStaticText"; 
    //public static Variable 
    public static String publicStaticText = "publicStaticText"; 

    /** 
     * Если интерфейс имеет один и только один абстрактный метод, этот интерфейс называется функциональным интерфейсом/функциональным интерфейсом (Functional interface) 
     * В Java8 и более поздних версиях интерфейсы функций могут использовать лямбда-выражения вместо выражений анонимных классов, то есть функциональные интерфейсы могут быть реализованы с использованием анонимных функций вместо анонимных внутренних классов. 
     * Можно добавить любое количество стандартных и статических методов 
     */ 
    @FunctionalInterface 
    public interface IHelloInner { 

        /** 
         * Абстрактный метод 
         */ 
        void sayHello();

    } 

    /** 
     * Функция-член 
     * 
     * @param локальная переменная iHelloOuter 
     */ 
    private void hello(IHelloOuter iHelloOuter) { 

        String localText = "localText"; 

        /** 
         * Локальный класс (локальный класс) 
         */ 
        class LocalClass реализует IHelloInner { 

            @Override 
            public void sayHello() { 
                //Вы можете использовать свойства внешнего класса 
                System.out.print("Hello LocalClass" + privateText); 
                System.out.print("Hello LocalClass" + privateStaticText); 
                System.out.print («Привет, LocalClass» + publicStaticText);
                System.out.print("Привет, LocalClass " + localText); 
            } 
        } 

    } 
} 
//编译后的LocalClass持有OuterClass的引用
class OuterClass$1LocalClass реализует IHelloInner { 
    OuterClass$1LocalClass(OuterClass var1, String var2) { 
        this.this$0 = var1; 
        this.val$localText = var2; 
    } 

    public void sayHello() { 
        System.out.print("Hello LocalClass" + OuterClass.access$000(this.this$0)); 
        System.out.print("Привет, LocalClass " + OuterClass.access$100()); 
        System.out.print("Привет, LocalClass " + OuterClass.publicStaticText); 
        System.out.print("Привет, LocalClass" + this.
    } 
}

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

Итак, если вы хотите изменить локальные переменные, можете ли вы? Ответ - нет. При необходимости переменную Effectively final официально предлагается заменить на класс Atomic для реализации.

переменное время жизни

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

Жизненный цикл переменной относится к процессу с момента создания переменной и выделения пространства памяти до момента уничтожения переменной и очистки занимаемого ею пространства памяти.

Изображение выше взято из: Обзор переменных-членов метода java и локальных переменных.

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

Ссылки на методы для лямбда-выражений

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

Вы используете лямбда-выражения для создания анонимных методов. Однако иногда лямбда-выражение не делает ничего, кроме вызова существующего метода. В таких случаях часто проще обратиться к существующему методу по имени. Ссылки на методы позволяют это сделать; это компактные, легко читаемые лямбда-выражения для методов, у которых уже есть имя.

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

Вы используете лямбда-выражения для создания анонимных методов. Однако иногда лямбда-выражения ничего не делают, кроме вызова существующего метода. В этих случаях часто проще обратиться к существующему методу по имени. Ссылки на методы позволяют это сделать; они представляют собой компактные, легко читаемые лямбда-выражения для методов, у которых уже есть имена.

Описание ссылок на методы взято из:

https://docs.oracle.com/javase/tutorial/java/javaOO/methodreferences.html

Ниже приведен некоторый синтаксис ссылки на метод в Java 8, существует четыре ссылки на методы:

тип

синтаксис

пример

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

ContainingClass :: staticMethodName

Person::compareByAge
MethodReferencesExamples::appendStrings

Обратитесь к методу экземпляра определенного объекта

содержащийОбъект :: instanceMethodName

myComparisonProvider::compareByName
myApp::appendStrings2

Относится к методу экземпляра любого объекта определенного типа.

ContainingType :: имя_метода

String::compareToIgnoreCase
String::concat

ссылка на конструктор

ClassName :: новый

Хэшсет:: новый

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

Преимущества и функции лямбда-выражений

Лямбда-выражения привносят в Java многие преимущества функционального программирования. Как и большинство ООП-языков, Java построен на классах и объектах и ​​рассматривает классы только как своих первоклассных граждан. Другие важные объекты программирования, такие как функции, отходят на второй план.

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

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

//Используем лямбда-выражение 
Operator<Integer> addOperation = (a, b) -> a + b; 

//Используем анонимный класс 
Operator<Integer> addOperation = new Operator<Integer>() { 
  @Override 
  public Integer process(Integer a, Целое число b) { 
    return a + b; 
  } 
};

Объектная ориентация неплохая, но она добавляет в программу много подробных деталей. Как видно из приведенного выше примера, фактическая используемая часть — это код в методе process(), а все остальные коды — это конструкции языка Java.

Функциональные интерфейсы Java 8 и лямбда-выражения помогают нам писать меньший и более лаконичный код, удаляя много стандартного кода. вот и все? Вместо того, чтобы обсуждать преимущества лямбда-выражений, лучше обсудить их роль. Что касается преимуществ, то их надо сравнивать, чтобы делать выводы, но ведь лямбда-выражения относятся к идее функционального программирования.Если вы хотите сравнить, то это кажется преувеличением, не так ли?

Что делает лямбда -выражение ? Или каково его значение (значение), есть ли разница в программировании на Java с лямбда-выражениями и без них? Просто для того, чтобы уменьшить количество кода? Чтобы понять значение лямбда-выражений , вы должны сначала понять, что такое функциональное программирование Ответы на приведенные выше вопросы находятся в статье Руана Ифэна « Предварительное исследование функционального программирования» , которая полностью зависит от вашего собственного понимания.

Здесь также есть соответствующие документы, чтобы поговорить об этом вопросе: Зачем нам Lambda Expression , ключевое содержание в авторской аранжировке выглядит следующим образом:

  1. Сокращение количества строк кода (уменьшение количества строк кода)

Хотя анонимные классы используются повсеместно, у них все еще есть много проблем. Первая серьезная проблема — сложность. Эти классы делают иерархию кода запутанной и сложной, что также известно как вертикальная проблема. Если сравнивать лямбда-выражения с анонимными классами, объем кода действительно сильно сокращается, и студенты, которые любят использовать синтаксический сахар, обязательно влюбятся в него.

  1. Sequential and Parallel Execution Support ( поддержка последовательного и параллельного выполнения )

Это требует отражения лямбда-выражений в сочетании с FunctionalInterface Lib, forEach, stream() и ссылками на методы . как использовать? И картинки, и тексты, яркие примеры.

  1. Передача поведения в методы

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

  1. Повышение эффективности с помощью лени

Как вы это говорите? В любом случае, лямбда-выражения ссылаются на существующие функции через ссылки на методы, что делает написание кода более эффективным.

Источник здесь: Функциональные интерфейсы Java 8 - JournalDev

Принцип реализации лямбда-выражения

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

Я не буду много вводить, вы можете прочитать это, если вам интересно

Чжэн Юди (старший научный сотрудник Oracle, кандидат компьютерных наук) «Углубленный дизассемблирование виртуальной машины Java» , главы можно прочитать бесплатно:

08 | Как JVM реализует invokedynamic? (начальство)

09 | Как JVM реализует invokedynamic? (Вниз)

Или вы можете прочитать это:

Вызов динамического метода JVM: invokedynamic

напиши в конце

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

Рекомендации

Официальная документация по спецификации виртуальной машины Java

Вехи JDK 8

OpenJDK: замыкания

Замыкания (лямбда-выражения) для языка программирования Java

JEP 126: лямбда-выражения и методы виртуального расширения

Лямбда-выражения в Java

Функциональный интерфейс Java 8 и лямбда-выражение

Лямбда-выражения Java

Что такое функциональный интерфейс в Java 8? @Функциональная аннотация и примеры

Анонимные классы и лямбда-выражения в Java

Функциональные интерфейсы Java 8

рекомендация

отblog.csdn.net/xiangang12202/article/details/122916258