HTML Diff
0 added 0 removed
Original 2026-01-01
Modified 2026-02-21
1 <p><a>#База знаний</a></p>
1 <p><a>#База знаний</a></p>
2 <ul><li>15 апр 2021</li>
2 <ul><li>15 апр 2021</li>
3 <li>0</li>
3 <li>0</li>
4 </ul><p>Что это такое, зачем нужно и как работает.</p>
4 </ul><p>Что это такое, зачем нужно и как работает.</p>
5 <p>Хлебом не корми - дай кому-нибудь про Java рассказать.</p>
5 <p>Хлебом не корми - дай кому-нибудь про Java рассказать.</p>
6 <p>Вы наверняка знакомы с ситуацией, когда в разных частях программы должен выполняться один и тот же код, а различие лишь в данных, которые он будет обрабатывать. И тогда кусок кода не дублируют, а поступают хитрее -<strong>создают метод</strong>. И этот метод вызывают в нужных местах.</p>
6 <p>Вы наверняка знакомы с ситуацией, когда в разных частях программы должен выполняться один и тот же код, а различие лишь в данных, которые он будет обрабатывать. И тогда кусок кода не дублируют, а поступают хитрее -<strong>создают метод</strong>. И этот метод вызывают в нужных местах.</p>
7 <p>Передача простых параметров в виде примитивов или объектов труда обычно не составляет. Но порой в метод требуется передавать не просто переменную, а исполняемый код.</p>
7 <p>Передача простых параметров в виде примитивов или объектов труда обычно не составляет. Но порой в метод требуется передавать не просто переменную, а исполняемый код.</p>
8 <p>Например, нам нужен метод, который работает с элементами массива, причём только с теми, что соответствуют некоторому условию. А само условие во время написания метода мы можем не знать (или оно будет меняться).</p>
8 <p>Например, нам нужен метод, который работает с элементами массива, причём только с теми, что соответствуют некоторому условию. А само условие во время написания метода мы можем не знать (или оно будет меняться).</p>
9 <p>Как поступить? Передавать реализующий условие код с помощью<strong>параметра метода</strong>! Да, в Java начиная с восьмой версии можно подобное делать. И сейчас вы узнаете как.</p>
9 <p>Как поступить? Передавать реализующий условие код с помощью<strong>параметра метода</strong>! Да, в Java начиная с восьмой версии можно подобное делать. И сейчас вы узнаете как.</p>
10 <p>Напишем методы, возвращающие сумму и произведение двух чисел:</p>
10 <p>Напишем методы, возвращающие сумму и произведение двух чисел:</p>
11 private int sum(int a, int b) { return a+b; } private int mult(int a, int b) { return a*b; }<p>А теперь объединим их в один - processTwoNumbers. Он будет принимать два параметра-числа и код, который их обрабатывает.</p>
11 private int sum(int a, int b) { return a+b; } private int mult(int a, int b) { return a*b; }<p>А теперь объединим их в один - processTwoNumbers. Он будет принимать два параметра-числа и код, который их обрабатывает.</p>
12 <p><strong>private int</strong>processTwoNumbers (<strong>int</strong>a,<strong>int</strong>b, [сюда передаётся код])</p>
12 <p><strong>private int</strong>processTwoNumbers (<strong>int</strong>a,<strong>int</strong>b, [сюда передаётся код])</p>
13 <p>Для выполнения метода sum третий параметр примет в качестве аргумента действие a+b, а для выполнения метода mult - a*b.</p>
13 <p>Для выполнения метода sum третий параметр примет в качестве аргумента действие a+b, а для выполнения метода mult - a*b.</p>
14 <p>Обратите внимание, что третьим аргументом может быть передан не любой код, а только тот, который принимает на вход два параметра заданного типа (у нас int) и возвращает переменную нужного типа (int).</p>
14 <p>Обратите внимание, что третьим аргументом может быть передан не любой код, а только тот, который принимает на вход два параметра заданного типа (у нас int) и возвращает переменную нужного типа (int).</p>
15 <p>Значит, надо как-то сообщить об этом компилятору - запретить будущим разработчикам передавать неподходящий код (вроде a+b+c).</p>
15 <p>Значит, надо как-то сообщить об этом компилятору - запретить будущим разработчикам передавать неподходящий код (вроде a+b+c).</p>
16 <p>Поможет в этом сигнатура метода. Она станет третьим параметром в нашем методе processTwoNumbers:</p>
16 <p>Поможет в этом сигнатура метода. Она станет третьим параметром в нашем методе processTwoNumbers:</p>
17 <p><strong>private int</strong>processTwoNumbers (<strong>int</strong>a,<strong>int</strong>b, [сигнатура метода])</p>
17 <p><strong>private int</strong>processTwoNumbers (<strong>int</strong>a,<strong>int</strong>b, [сигнатура метода])</p>
18 <p>Но как записать третий параметр, чтобы сигнатура самого метода processTwoNumbers не разрослась до нечитабельности? Этот вопрос разработчики Java решили изящно. Они придумали функциональные интерфейсы.</p>
18 <p>Но как записать третий параметр, чтобы сигнатура самого метода processTwoNumbers не разрослась до нечитабельности? Этот вопрос разработчики Java решили изящно. Они придумали функциональные интерфейсы.</p>
19 <p><strong>Функциональный</strong><strong>интерфейс</strong> - это интерфейс, который содержит<strong>ровно один</strong>абстрактный метод, то есть описание метода без тела. Статические методы и методы по умолчанию при этом не в счёт, их в функциональном интерфейсе может быть сколько угодно.</p>
19 <p><strong>Функциональный</strong><strong>интерфейс</strong> - это интерфейс, который содержит<strong>ровно один</strong>абстрактный метод, то есть описание метода без тела. Статические методы и методы по умолчанию при этом не в счёт, их в функциональном интерфейсе может быть сколько угодно.</p>
20 <p>Когда параметром метода является функциональный интерфейс, при вызове этого метода одним из аргументов должен быть блок кода.</p>
20 <p>Когда параметром метода является функциональный интерфейс, при вызове этого метода одним из аргументов должен быть блок кода.</p>
21 <p>Передаваемый блок кода должен удовлетворять следующему условию: его сигнатура должна совпадать с сигнатурой единственного абстрактного метода функционального интерфейса.</p>
21 <p>Передаваемый блок кода должен удовлетворять следующему условию: его сигнатура должна совпадать с сигнатурой единственного абстрактного метода функционального интерфейса.</p>
22 <p>Звучит непросто, поясним на примере:</p>
22 <p>Звучит непросто, поясним на примере:</p>
23 @FunctionalInterface public interface ToIntBiFunction&lt;T, U&gt; { /** * Applies this function to the given arguments. * * @param t the first function argument * @param u the second function argument * @return the function result */ int applyAsInt(T t, U u); }<p><strong>Важно.</strong>В Java есть несколько готовых функциональных интерфейсов с разным числом и типами входных-выходных параметров. (Как раз из таких ToIntBiFunction выше.) А если мы создаём новый функциональный интерфейс, то важно не забыть аннотацию @FunctionalInterface. Увидев её, компилятор проверит, что интерфейс и правда является функциональным.</p>
23 @FunctionalInterface public interface ToIntBiFunction&lt;T, U&gt; { /** * Applies this function to the given arguments. * * @param t the first function argument * @param u the second function argument * @return the function result */ int applyAsInt(T t, U u); }<p><strong>Важно.</strong>В Java есть несколько готовых функциональных интерфейсов с разным числом и типами входных-выходных параметров. (Как раз из таких ToIntBiFunction выше.) А если мы создаём новый функциональный интерфейс, то важно не забыть аннотацию @FunctionalInterface. Увидев её, компилятор проверит, что интерфейс и правда является функциональным.</p>
24 <p>Функциональный интерфейс ToIntBiFunction&lt;T, U&gt; подходит к тому примеру, с которого мы начинали. Это значит, что мы можем передать в него аргументом код, который:</p>
24 <p>Функциональный интерфейс ToIntBiFunction&lt;T, U&gt; подходит к тому примеру, с которого мы начинали. Это значит, что мы можем передать в него аргументом код, который:</p>
25 <ul><li>принимает на вход два параметра (T t, U u). T и U указывают на то, что аргументы могут быть разных типов. Например, Long и String. Для нас это даже избыточно, у нас они одного типа - int;</li>
25 <ul><li>принимает на вход два параметра (T t, U u). T и U указывают на то, что аргументы могут быть разных типов. Например, Long и String. Для нас это даже избыточно, у нас они одного типа - int;</li>
26 <li>возвращает значение типа int.</li>
26 <li>возвращает значение типа int.</li>
27 </ul><p>Вот что получится:</p>
27 </ul><p>Вот что получится:</p>
28 public void processTwoNumbers(int a, int b, ToIntBiFunction&lt;Integer, Integer&gt; function){ //code }<p>Кусочек ToIntBiFunction&lt;Integer, Integer&gt; говорит: передавай сюда метод с такой же сигнатурой, как у метода внутри меня.</p>
28 public void processTwoNumbers(int a, int b, ToIntBiFunction&lt;Integer, Integer&gt; function){ //code }<p>Кусочек ToIntBiFunction&lt;Integer, Integer&gt; говорит: передавай сюда метод с такой же сигнатурой, как у метода внутри меня.</p>
29 <p>Чтобы внутри метода processTwoNumbers выполнить переданный код, нужно вызвать метод из функционального интерфейса:</p>
29 <p>Чтобы внутри метода processTwoNumbers выполнить переданный код, нужно вызвать метод из функционального интерфейса:</p>
30 public void processTwoNumbers(int a, int b, ToIntBiFunction&lt;Integer, Integer&gt; function){ function.applyAsInt(a, b); }<p>Вот мы и добрались до лямбда-выражений.</p>
30 public void processTwoNumbers(int a, int b, ToIntBiFunction&lt;Integer, Integer&gt; function){ function.applyAsInt(a, b); }<p>Вот мы и добрались до лямбда-выражений.</p>
31 <p>Это компактный синтаксис, заимствованный из λ-исчисления, для передачи кода в качестве параметра в другой код.</p>
31 <p>Это компактный синтаксис, заимствованный из λ-исчисления, для передачи кода в качестве параметра в другой код.</p>
32 <p>По сути, это анонимный (без имени) класс или метод. Так как всё в Java (за исключением примитивных типов) - это объекты, лямбды тоже должны быть связаны с конкретным объектным типом. Как вы догадались, он называется функциональным интерфейсом.</p>
32 <p>По сути, это анонимный (без имени) класс или метод. Так как всё в Java (за исключением примитивных типов) - это объекты, лямбды тоже должны быть связаны с конкретным объектным типом. Как вы догадались, он называется функциональным интерфейсом.</p>
33 <p>То есть лямбда-выражение не выполняется само по себе, а нужно для реализации метода, который определён в функциональном интерфейсе.</p>
33 <p>То есть лямбда-выражение не выполняется само по себе, а нужно для реализации метода, который определён в функциональном интерфейсе.</p>
34 <p>Не будь лямбд, вызывать метод processTwoNumbers каждый раз приходилось бы так:</p>
34 <p>Не будь лямбд, вызывать метод processTwoNumbers каждый раз приходилось бы так:</p>
35 ToIntBiFunction&lt;Integer, Integer&gt; biFunction = new ToIntBiFunction&lt;&gt;() { @Override public int applyAsInt(Integer a, Integer b) { return a + b; } }; processTwoNumbers(1, 2, biFunction);<p><strong>Примечание.</strong>biFunction в примере создана с использованием анонимных классов. Без этого нам пришлось бы создавать класс, реализующий интерфейс ToIntBiFunction, и объявлять в этом классе метод applyAsInt. А с анонимным классом мы всё это сделали на лету.</p>
35 ToIntBiFunction&lt;Integer, Integer&gt; biFunction = new ToIntBiFunction&lt;&gt;() { @Override public int applyAsInt(Integer a, Integer b) { return a + b; } }; processTwoNumbers(1, 2, biFunction);<p><strong>Примечание.</strong>biFunction в примере создана с использованием анонимных классов. Без этого нам пришлось бы создавать класс, реализующий интерфейс ToIntBiFunction, и объявлять в этом классе метод applyAsInt. А с анонимным классом мы всё это сделали на лету.</p>
36 <p>В примере выше всё, кроме одной строчки, избыточно. За содержательную часть (логику работы) отвечает только одно выражение return a + b, а всё остальное - техническая шелуха. И её пришлось написать многовато, даже чтобы просто передать методу код сложения двух чисел.</p>
36 <p>В примере выше всё, кроме одной строчки, избыточно. За содержательную часть (логику работы) отвечает только одно выражение return a + b, а всё остальное - техническая шелуха. И её пришлось написать многовато, даже чтобы просто передать методу код сложения двух чисел.</p>
37 <p>Здесь и вступают в игру лямбды. С ними можно сократить создание biFunction всего до десяти символов!</p>
37 <p>Здесь и вступают в игру лямбды. С ними можно сократить создание biFunction всего до десяти символов!</p>
38 <p>Лямбда строится так: (параметры) -&gt; (код метода)</p>
38 <p>Лямбда строится так: (параметры) -&gt; (код метода)</p>
39 <p>А наша лямбда будет такой:</p>
39 <p>А наша лямбда будет такой:</p>
40 <p>ToIntBiFunction&lt;Integer, Integer&gt; biFunction =<strong>(a, b) -&gt; a + b</strong>;</p>
40 <p>ToIntBiFunction&lt;Integer, Integer&gt; biFunction =<strong>(a, b) -&gt; a + b</strong>;</p>
41 <p>И всё! Этот<strong>блок из 10 символов</strong>можно передавать как аргумент методу, ожидающему функциональный интерфейс в качестве параметра. Причём чаще всего обходятся без промежуточной переменной - передают напрямую лямбду:</p>
41 <p>И всё! Этот<strong>блок из 10 символов</strong>можно передавать как аргумент методу, ожидающему функциональный интерфейс в качестве параметра. Причём чаще всего обходятся без промежуточной переменной - передают напрямую лямбду:</p>
42 processTwoNumbers(1, 2, (a, b) -&gt; a+b);<p>Компилятор проверит, что лямбда подходит функциональному интерфейсу - принимает нужное число параметров нужного типа. Напомню, в нашем примере задействован функциональный интерфейс ToIntBiFunction. Сигнатура его единственного абстрактного метода содержит два параметра (Integer a, Integer b).</p>
42 processTwoNumbers(1, 2, (a, b) -&gt; a+b);<p>Компилятор проверит, что лямбда подходит функциональному интерфейсу - принимает нужное число параметров нужного типа. Напомню, в нашем примере задействован функциональный интерфейс ToIntBiFunction. Сигнатура его единственного абстрактного метода содержит два параметра (Integer a, Integer b).</p>
43 <p>Например, такой вот вызов метода не скомпилируется, потому что передан всего один параметр:</p>
43 <p>Например, такой вот вызов метода не скомпилируется, потому что передан всего один параметр:</p>
44 processTwoNumbers(5, 6, (a) -&gt; 5 * a);<p>Лямбды записывают<a>по-разному</a>. Мы рассмотрели только один вариант.</p>
44 processTwoNumbers(5, 6, (a) -&gt; 5 * a);<p>Лямбды записывают<a>по-разному</a>. Мы рассмотрели только один вариант.</p>
45 <p>Много где. Довольно частый случай - обход элементов в цикле:</p>
45 <p>Много где. Довольно частый случай - обход элементов в цикле:</p>
46 List&lt;Integer&gt; integers = Arrays.asList(1, 2, 3, 4, 5); integers.forEach(item -&gt; System.out.println(item));<p>Ещё лямбды работают в компараторах при сортировке. Допустим, нужно отсортировать коллекцию по последней букве каждого слова:</p>
46 List&lt;Integer&gt; integers = Arrays.asList(1, 2, 3, 4, 5); integers.forEach(item -&gt; System.out.println(item));<p>Ещё лямбды работают в компараторах при сортировке. Допустим, нужно отсортировать коллекцию по последней букве каждого слова:</p>
47 List&lt;String&gt; colors = Arrays.asList("Black", "White", "Red"); Collections.sort(colors, (o1, o2) -&gt; { String o1LastLetter = o1.substring(o1.length() - 1); String o2LastLetter = o2.substring(o2.length() - 1); return o1LastLetter.compareTo(o2LastLetter); });<p>Редко обходятся без лямбд при работе с <a>коллекциями</a>вместе со <a>Stream API</a>. В следующем примере фильтруем стрим по значению (filter), меняем каждый элемент (map) и собираем в список (collect):</p>
47 List&lt;String&gt; colors = Arrays.asList("Black", "White", "Red"); Collections.sort(colors, (o1, o2) -&gt; { String o1LastLetter = o1.substring(o1.length() - 1); String o2LastLetter = o2.substring(o2.length() - 1); return o1LastLetter.compareTo(o2LastLetter); });<p>Редко обходятся без лямбд при работе с <a>коллекциями</a>вместе со <a>Stream API</a>. В следующем примере фильтруем стрим по значению (filter), меняем каждый элемент (map) и собираем в список (collect):</p>
48 final double currencyUsdRub = 80; List&lt;Double&gt; pricesRub = Arrays.asList(25d, 50d , 60d, 12d, 45d, 89d); List&lt;Double&gt; pricesUsdGreater50Rub = pricesRub.stream() .filter(d -&gt; d &gt; 50) // используем функциональный интерфейс Predicate&lt;T&gt; .map(d -&gt; d / currencyUsdRub) // используем функциональный интерфейс Function&lt;T, R&gt; .collect(Collectors.toList());<p>Функциональные интерфейсы в Java 8 избавили разработчиков от чудовищно громоздкого синтаксиса с анонимными классами (когда требовалось передавать некую функциональность в метод) и позволили использовать компактные лямбда-выражения и ссылки на методы.</p>
48 final double currencyUsdRub = 80; List&lt;Double&gt; pricesRub = Arrays.asList(25d, 50d , 60d, 12d, 45d, 89d); List&lt;Double&gt; pricesUsdGreater50Rub = pricesRub.stream() .filter(d -&gt; d &gt; 50) // используем функциональный интерфейс Predicate&lt;T&gt; .map(d -&gt; d / currencyUsdRub) // используем функциональный интерфейс Function&lt;T, R&gt; .collect(Collectors.toList());<p>Функциональные интерфейсы в Java 8 избавили разработчиков от чудовищно громоздкого синтаксиса с анонимными классами (когда требовалось передавать некую функциональность в метод) и позволили использовать компактные лямбда-выражения и ссылки на методы.</p>
49 <p>До восьмой версии Java разработчики обходились без лямбда-выражений. Лямбды стали для них очередным синтаксическим сахаром.</p>
49 <p>До восьмой версии Java разработчики обходились без лямбда-выражений. Лямбды стали для них очередным синтаксическим сахаром.</p>
50 <p>Сперва синтаксическим сахаром были функциональные интерфейсы, они позволили оперировать блоком кода, который выполняется когда нужно, но реализации были слишком громоздкими. А с лямбдами функциональные интерфейсы стали записываться короче. Так что лямбды - не просто синтаксический сахар, а синтаксический сахар синтаксического сахара.</p>
50 <p>Сперва синтаксическим сахаром были функциональные интерфейсы, они позволили оперировать блоком кода, который выполняется когда нужно, но реализации были слишком громоздкими. А с лямбдами функциональные интерфейсы стали записываться короче. Так что лямбды - не просто синтаксический сахар, а синтаксический сахар синтаксического сахара.</p>
51 <p>Теперь на Java можно писать программы в стиле функциональных языков программирования (это когда программа записывается как последовательное применение функций к некоторым значениям и другим функциям, а не как сложная структура из циклов, условных операторов и перекладывания значений туда-сюда). Удивительно, как легко превратить массивные структуры кода в изящные цепочки вызовов, и всё это благодаря лямбдам и функциональным интерфейсам.</p>
51 <p>Теперь на Java можно писать программы в стиле функциональных языков программирования (это когда программа записывается как последовательное применение функций к некоторым значениям и другим функциям, а не как сложная структура из циклов, условных операторов и перекладывания значений туда-сюда). Удивительно, как легко превратить массивные структуры кода в изящные цепочки вызовов, и всё это благодаря лямбдам и функциональным интерфейсам.</p>
52 <p>Официальная документация по лямбдам<a>здесь</a>.</p>
52 <p>Официальная документация по лямбдам<a>здесь</a>.</p>
53 <a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>
53 <a><b>Бесплатный курс по Python ➞</b>Мини-курс для новичков и для опытных кодеров. 4 крутых проекта в портфолио, живое общение со спикером. Кликните и узнайте, чему можно научиться на курсе. Смотреть программу</a>