Эта статья из серии « Тур по новым возможностям Java SE 8 » глубоко погрузится в понимание лямбда-выражений . Я покажу вам несколько разных способов использования лямбда-выражений. Все они имеют общую реализацию функциональных интерфейсов. Я объясню, как компилятор выводит информацию из кода, такую как конкретные типы переменных и что на самом деле происходит в фоновом режиме.
В предыдущей статье « Обзор новых функций Java SE 8: большие перемены в мире разработки Java », где я рассказал о том, что мы собираемся исследовать в этой серии. Я начал с ознакомления с основными функциями Java SE 8 , после чего последовал процесс установки JDK8 на платформах Microsoft Windows и Apple Mac OS X с важными советами и примечаниями, о которых следует позаботиться.
Наконец, мы прошли разработку консольного приложения на основе выражения Lambda, чтобы убедиться, что мы установили Java SE 8, вероятно.
Исходный код размещен на моей учетной записи Github: клон от ЗДЕСЬ .
Что такое лямбда-выражение?
Возможно, самая известная новая функция Java SE 8 называется Project Lambda — попытка привнести Java в мир функционального программирования .
В терминологии информатики;
Лямбда — это анонимная функция. То есть функция без имени.
На Java;
Все функции являются членами классов и называются методами. Чтобы создать метод, вам нужно определить класс, членом которого он является.
Лямбда-выражение в Java SE 8 позволяет вам определять класс и отдельный метод с очень лаконичным синтаксисом, реализуя интерфейс, который имеет один абстрактный метод.
Давайте разберемся с идеей.
Лямбда-выражения позволяют разработчикам упростить и сократить свой код. Делая это более читабельным и ремонтопригодным. Это приводит к удалению более подробных объявлений классов .
Давайте посмотрим на несколько фрагментов кода.
- Реализация интерфейса: до Java SE 8, если вы хотите создать поток, вы сначала должны определить класс, который реализует работающий интерфейс. Это интерфейс, который имеет единственный абстрактный метод с именем Run, который не принимает аргументов. Вы можете определить класс в своем собственном кодовом файле. Файл с именем MyRunnable.java . И вы могли бы назвать класс MyRunnable, как я сделал здесь. И тогда вы реализуете один абстрактный метод.
010203040506070809101112
public
class
MyRunnable
implements
Runnable {
@Override
public
void
run() {
System.out.println(
"I am running"
);
}
public
static
void
main(String[] args) {
MyRunnable r1 =
new
MyRunnable();
new
Thread(r1).start();
}
}
В этом примере моя реализация выводит литеральную строку в консоль. Затем вы возьмете этот объект и передадите его экземпляру класса потока. Я создаю экземпляр моего запускаемого объекта как объекта с именем r1. Передача его конструктору потока и вызов метода start потока. Мой код теперь будет выполняться в своем собственном потоке и в собственном пространстве памяти.
- Реализация внутреннего класса: Вы можете немного улучшить этот код, вместо того, чтобы объявлять свой класс в отдельном файле, вы можете объявить его как одноразовый класс, известный как внутренний класс , локальный для метода, в котором он используется.
01020304050607080910
public
static
void
main(String[] args) {
Runnable r1 =
new
Runnable() {
@Override
public
void
run() {
System.out.println(
"I am running"
);
}
};
new
Thread(r1).start();
}
Итак, теперь я снова создаю объект с именем r1, но я вызываю метод конструктора интерфейса напрямую. И еще раз, реализуя это единственный абстрактный метод. Затем я передаю объект в конструктор потока.
- Реализация анонимного класса. И вы можете сделать его еще более сжатым, объявив класс как анонимный класс , названный так, потому что ему никогда не дают имя. Я создаю экземпляр запускаемого интерфейса и сразу передаю его в конструктор потоков. Я все еще реализую метод run и все еще вызываю метод start потока.
12345678
public
static
void
main(String[] args) {
new
Thread(
new
Runnable() {
@Override
public
void
run() {
System.out.println(
"I am running"
);
}
}).start();
}
- Использование лямбда-выражения: в Java SE 8 вы можете повторно кодировать этот код, чтобы значительно уменьшить его и сделать его более читабельным. Лямбда-версия может выглядеть следующим образом.
1234
public
static
void
main(String[] args) {
Runnable r1 = () -> System.out.println(
"I am running"
);
new
Thread(r1).start();
}
Я объявляю объект с типом runnable, но теперь я использую одну строку кода, чтобы объявить реализацию одного абстрактного метода, а затем еще раз передаю объект в конструктор Thread. Вы все еще реализуете интерфейс runnable и вызываете его метод run, но вы делаете это с гораздо меньшим количеством кода. Кроме того, это может быть улучшено следующим образом:
1234public
static
void
main(String[] args) {
new
Thread(() -> System.out.println(
"I am running"
)).start();
}
Вот важная цитата из раннего спецификационного документа о Project Lambda.
Лямбда-выражения могут появляться только в тех местах, где они будут назначены переменной, тип которой является функциональным интерфейсом.
Цитата Брайана ГетцаДавайте разберемся с этим, чтобы понять, что происходит.
Каковы функциональные интерфейсы?
Функциональный интерфейс — это интерфейс, который имеет только один пользовательский абстрактный метод. То есть тот, который не унаследован от класса объекта. У Java много таких интерфейсов, таких как Runnable, Comparable, Callable, TimerTask и многие другие.
До Java 8 они были известны как Единый абстрактный метод или интерфейсы SAM . В Java 8 мы теперь называем их функциональными интерфейсами .
Синтаксис лямбда-выражения:
Это лямбда-выражение возвращает реализацию работоспособного интерфейса; он состоит из двух частей, разделенных новым битом синтаксиса, называемым маркером стрелки или оператором Lambda . Первая часть лямбда-выражения перед маркером стрелки является сигнатурой реализуемого вами метода.
В этом примере это метод без аргументов, поэтому он представлен только круглыми скобками. Но если я реализую метод, который принимает аргументы, я бы просто дал имена аргументов. Я не должен объявлять их типы.
Поскольку интерфейс имеет только один абстрактный метод, типы данных уже известны. И одной из целей лямбда-выражения является устранение ненужного синтаксиса. Вторая часть выражения после маркера со стрелкой — это реализация тела одного метода.
Если это всего лишь одна строка кода, как в этом примере, вам больше ничего не нужно. Чтобы реализовать тело метода с несколькими операторами, заключите их в фигурные скобки .
1
2
3
4
|
Runnable r = ( ) -> { System.out.println( "Hello!" ); System.out.println( "Lambda!" ); }; |
Лямбда Цели:
Лямбда-выражения могут уменьшить объем кода, который вам нужно написать, и количество пользовательских классов, которые вы должны создавать и поддерживать.
Если вы реализуете интерфейс для одноразового использования, не всегда имеет смысл создавать еще один файл кода или еще один именованный класс. Лямбда-выражение может определять анонимную реализацию для однократного использования и значительно оптимизировать ваш код.
Определение и создание функционального интерфейса
Чтобы начать изучать лямбда-выражения, я создам новый функциональный интерфейс. Интерфейс с одним абстрактным методом, а затем я реализую этот интерфейс с помощью лямбда-выражения.
Вы можете использовать мой проект исходного кода «JavaSE8-Features», размещенный на github, для навигации по коду проекта.
-
Метод без аргументов, лямбда-реализация
В моем исходном коде я фактически поместил интерфейс в собственный подпакет, заканчивающийся lambda.interfaces . И я назову интерфейс HelloInterface . Чтобы реализовать интерфейс с лямбда-выражением, он должен иметь единственный абстрактный метод. Я объявлю открытый метод, который возвращает void, и назову его doGreeting . Он не будет принимать никаких аргументов. Это все, что вам нужно сделать, чтобы создать интерфейс, который можно использовать с лямбда-выражениями. При желании вы можете использовать новую аннотацию, добавленную в Java SE 8, под названием « Функциональный интерфейс» .
01020304050607080910/**
*
* @author mohamed_taman
*/
@FunctionalInterface
public
interface
HelloInterface {
void
doGreeting();
}
Теперь я готов создать новый класс UseHelloInterface в пакете lambda.impl , который будет создавать экземпляр моего функционального интерфейса ( HelloInterface ) следующим образом:
0102030405060708091011121314/**
* @author mohamed_taman
*/
public
class
UseHelloInterface {
public
static
void
main(String[] args) {
HelloInterface hello = ()-> out.println(
"Hello from Lambda expression"
);
hello.doGreeting();
}
}
Запустите файл и проверьте результат, он должен запуститься и вывести следующее.
1234------------------------------------------------------------------------------------
--- exec-maven-plugin:
1.2
.
1
:exec (
default
-cli) @ Java8Features ---
Hello from Lambda expression
------------------------------------------------------------------------------------
Вот как может выглядеть код, когда вы работаете с одним абстрактным методом, который не принимает никаких аргументов. Давайте посмотрим, как это выглядит с аргументами.
-
Метод с любым аргументом, лямбда-реализация
Под лямбда.интерфейс . Я создам новый интерфейс и назову его CalculatorInterface . Затем я объявлю открытый метод, который возвращает void, и назову его doCalculate , который получит два целочисленных аргумента value1 и value2 .
01020304050607080910/**
* @author mohamed_taman
*/
@FunctionalInterface
public
interface
CalculatorInterface {
public
void
doCalculate(
int
value1,
int
value2);
}
Теперь я готов создать новый класс Use CalculatorInterface в пакете lambda.impl , который создаст экземпляр моего функционального интерфейса ( CalculatorInterface ) следующим образом:
123456789public
static
void
main(String[] args) {
CalculatorInterface calc = (v1, v2) -> {
int
result = v1 * v2;
out.println(
"The calculation result is: "
+ result);
};
calc.doCalculate(
10
,
5
);
}
Обратите внимание на аргументы doCalculate () , они были названы value1 и value2 в интерфейсе, но вы можете назвать их как угодно здесь. Я назову их v1 и v2. Мне не нужно вводить int перед именами аргументов; эта информация уже известна, потому что компилятор может вывести эту информацию из сигнатуры метода функционального интерфейса. Запустите файл и проверьте результат, он должен запустить и вывести следующее.
12345------------------------------------------------------------------------------------
--- exec-maven-plugin:
1.2
.
1
:exec (
default
-cli) @ Java8Features ---
The calculation result is:
50
------------------------------------------------------------------------------------
BUILD SUCCESS
Всегда имейте в виду следующее правило:
Опять же, вы должны следовать этому правилу, что интерфейс может иметь только один абстрактный метод . Затем этот интерфейс и его единственный абстрактный метод могут быть реализованы с помощью лямбда-выражения .
-
Использование встроенных функциональных интерфейсов с лямбдами
Ранее я описывал, как использовать лямбда-выражение для реализации интерфейса, который вы создали сами. Теперь я покажу лямбда-выражения со встроенными интерфейсами. Интерфейсы, которые являются частью среды выполнения Java. Я буду использовать два примера. Я работаю в пакете под названием lambda.builtin , который является частью файлов упражнений. И я начну с этого класса. UseThreading . В этом классе я реализую интерфейс Runnable . Этот интерфейс является частью многопоточной архитектуры Java. Здесь я сосредоточен на том, как вы кодируете, а не на том, как он работает. Я собираюсь показать, как использовать лямбда-выражения для замены этих внутренних классов. Я закомментирую код, который объявляет два объекта. Затем я повторно объявлю их и сделаю реализацию с лямбдами. Итак, начнем.
010203040506070809101112131415161718192021222324252627282930313233343536373839public
static
void
main(String[] args) {
//Old version
// Runnable thrd1 = new Runnable(){
// @Override
// public void run() {
// out.println("Hello Thread 1.");
// }
//};
/*
*****************************************
* Using lambda expression inner classes *
*****************************************
*/
Runnable thrd1 = () -> out.println(
"Hello Thread 1."
);
new
Thread(thrd1).start();
// Old Version
/*
new Thread(new Runnable() {
@Override
public void run() {
out.println("Hello Thread 2.");
}
}).start();
*/
/*
******************************************
* Using lambda expression anonymous class *
******************************************
*/
new
Thread(() -> out.println(
"Hello Thread 2."
)).start();
}
Давайте посмотрим на другой пример. Я буду использовать Компаратор . Comparator — это еще один функциональный интерфейс в Java, который имеет единственный абстрактный метод. Этот метод является методом сравнения. Откройте файл класса UseComparator и проверьте закомментированный бит кода, который является фактическим кодом, прежде чем преобразовать его в лямбда-выражение.
0102030405060708091011121314151617181920212223242526272829303132public
static
void
main(String[] args) {
List<string> values =
new
ArrayList();
values.add(
"AAA"
);
values.add(
"bbb"
);
values.add(
"CCC"
);
values.add(
"ddd"
);
values.add(
"EEE"
);
//Case sensitive sort operation
sort(values);
out.println(
"Simple sort:"
);
print(values);
// Case insensetive sort operation with anonymous class
/*
Collections.sort(values, new Comparator<string>() {
@Override
public int compare(String o1, String o2) {
return o1.compareToIgnoreCase(o2);
}
});
*/
// Case insensetive sort operation with Lambda
sort(values,(o1, o2) -> o1.compareToIgnoreCase(o2));
out.println(
"Sort with Comparator"
);
print(values);
}
Как и прежде, он не дает вам никакого выигрыша в производительности . Базовая функциональность точно такая же. Независимо от того, объявляете ли вы свои собственные классы , используете ли вы внутренние или анонимные внутренние классы или лямбда-выражения , все зависит от вас.
В следующей статье этой серии мы рассмотрим и создадим код для обхода коллекций с использованием лямбда-выражения, фильтрации коллекций с помощью интерфейсов Predicate , обхода коллекций с помощью ссылок на методы, реализации методов по умолчанию в интерфейсах и, наконец, реализации статических методов в интерфейсах.
Ресурсы:
- Учебники по Java, лямбда-выражения
- JSR 310: API даты и времени
- JSR 337: Java SE 8 Release Содержание
- Сайт OpenJDK
- Платформа Java, стандартное издание 8, спецификация API
Ссылка: | Обзор новых возможностей Java SE 8: функциональное программирование с использованием Lambda Expression от нашего партнера по JCG Мохамеда Тамана в блоге « Улучшите свою жизнь с помощью науки и искусства» . |