Это 22-й день моего участия в ноябрьском испытании обновлений.Подробности о событии:Вызов последнего обновления 2021 г.
Зачем использовать лямбда-выражения
Давайте сначала рассмотрим несколько фрагментов кода, которые часто встречались до появления Java8:
Создайте тему и начните
// 创建线程
public class Worker implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
doWork();
}
}
}
// 启动线程
Worker w = new Worker();
new Thread(w).start();
сравнивать массивы
// 定义一个比较器
public class LengthComparator implements Comparator<String> {
@Override
public int compare(String first, String second) {
return Integer.compare(first.length(), second.length());
}
}
//对字符数组进行比较
Arrays.sort(words, new LengthComparator());
Добавить событие нажатия на кнопку
public void onClick(Button button) {
button.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent e) {
System.out.println("button clicked.");
}
});
}
Для этих трех кусков кода мы стали обычным явлением.
Но их проблема также очень заметна: этошумперебор! Чтобы реализовать функцию сравнения массивов, вам нужно написать как минимум 5 строк кода, но на самом деле нам важна только одна строка кода!
Сложная и избыточная реализация кода Java всегда подвергалась критике со стороны программистов.К счастью, с появлением языка платформы JVM Scala и популярности стилей функционального программирования, Oracle внесла революционные изменения в 8-ю серию версий Java.Представила ряд функциональных функции синтаксиса стиля программирования, такие как лямбда-выражения и потоки.
Если используются лямбда-выражения, реализация трех приведенных выше фрагментов кода станет предельно лаконичной.
Создайте поток и запустите его (в версии Lambda)
new Thread(() -> {
for (int i = 0; i < 100; i++) {
doWork();
}
}).start();
Сравните массивы (в версии Lambda)
Arrays.sort(words, (first, second) -> Integer.compare(first.length(), second.length())
Добавить событие нажатия на кнопку (используя версию Lambda)
button.addActionListener((event) -> System.out.println("button clicked."));
Как насчет этого? С лямбда-выражениями код стал достаточно лаконичным, чтобы можно было поместитьВсе внимание сосредоточено на бизнес-коде.
Синтаксис лямбда-выражения
Формат: (параметр) -> выражение
в:
- Параметры могут быть 0-n. Если параметров несколько, разделите их запятыми (,). При наличии одного параметра скобки () можно опустить, при отсутствии параметра скобки () опустить нельзя. [Это недостаточно чисто, и все же немного хуже, чем scala! ], имя типа может быть добавлено перед параметром, но его можно опустить из-за функции автоматического вывода типа.
- Выражение может быть однострочным выражением или несколькими операторами. Если это несколько операторов, их необходимо заключить в фигурные скобки {}.
- Выражению не нужно отображать возвращаемый результат выполнения, оно автоматически выводится из контекста. Вот некоторые примеры:
параметр
event -> System.out.println("button clicked.")
несколько параметров
(first, second) -> Integer.compare(first.length(), second.length()
0 параметров
() -> System.out.println("what are you nongshalei?")
блок выражения
() -> {for (int i = 0; i < 100; i++) { doWork();}}
функциональный интерфейс
В Java8 добавлена новая аннотация:@FunctionalInterface, функциональный интерфейс.
Что такое функциональный интерфейс? Он содержит следующие функции:
- В интерфейсе есть только один абстрактный метод, но разрешены методы по умолчанию и статические методы.
- @FunctionalInterfaceАннотация не обязательна, но рекомендуется ее добавить, чтобы компилятор мог проверить, есть ли в интерфейсе только один абстрактный метод.
Суть лямбда-выражения — анонимная реализация функционального интерфейса. Он просто выражает исходную реализацию интерфейса в синтаксисе, больше похожем на функциональное программирование.
Пакет Java8 java.util.function имеет встроенное большое количество функциональных интерфейсов, а именно:
функциональный интерфейс | Тип параметра | возвращаемый тип | имя метода | описывать |
---|---|---|---|---|
Supplier | никто | T | get | производит данные типа T |
Consumer | T | void | accept | Потребляйте данные типа T |
BiConsumer<T,U> | T,U | void | accept | Потреблять данные типа T и типа U |
Function<T,R> | T | R | apply | Преобразование данных типа параметра T в данные типа R посредством обработки функций |
BiFunction<T,U,R> | T,U | R | apply | Преобразование данных с типами параметров T и U в данные типа R посредством обработки функций |
UnaryOperator | T | T | apply | Унарная операция над типом T по-прежнему возвращает тип T |
BinaryOperator | T,T | T | apply | бинарная операция над типом T по-прежнему возвращает тип T |
Predicate | T | void | test | Выполнить функцию для типа T, возвращая логическое значение |
BiPredicate<T,U> | T,U | void | test | Выполнить функцию для типов T и U, возвращая логическое значение |
Отсюда видно, что:
- Встроенные функциональные интерфейсы в основном делятся на четыре категории: поставщик, потребитель, функция и предикат. Оператор является частным случаем Функции.
- За исключением того, что Поставщик не предоставляет двоичные параметры (это связано с тем, что java не поддерживает несколько возвращаемых значений), остальные три категории предоставляют двоичные входные параметры.
Вот исчерпывающий пример:
public class FunctionalCase {
public static void main(String[] args) {
String words = "Hello, World";
String lowerWords = changeWords(words, String::toLowerCase);
System.out.println(lowerWords);
String upperWords = changeWords(words, String::toUpperCase);
System.out.println(upperWords);
int count = wordsToInt(words, String::length);
System.out.println(count);
isSatisfy(words, w -> w.contains("hello"));
String otherWords = appendWords(words, ()->{
List<String> allWords = Arrays.asList("+abc", "->efg");
return allWords.get(new Random().nextInt(2));
});
System.out.println(otherWords);
consumeWords(words, w -> System.out.println(w.split(",")[0]));
}
public static String changeWords(String words, UnaryOperator<String> func) {
return func.apply(words);
}
public static int wordsToInt(String words, Function<String, Integer> func) {
return func.apply(words);
}
public static void isSatisfy(String words, Predicate<String> func) {
if (func.test(words)) {
System.out.println("test pass");
} else {
System.out.println("test failed.");
}
}
public static String appendWords(String words, Supplier<String> func) {
return words + func.get();
}
public static void consumeWords(String words, Consumer<String> func) {
func.accept(words);
}
}
Если вам кажется, что этих встроенных функциональных интерфейсов недостаточно, вы также можете настроить свои собственные функциональные интерфейсы для удовлетворения большего количества потребностей.
ссылка на метод
Если у лямбда-выражения уже есть реализованный метод, его можно упростить с помощью ссылки на метод. Синтаксис ссылок на методы следующий:
- объект :: метод экземпляра
- класс::статический метод
- класс:: метод экземпляра
Как и вышеупомянутое лямбда-выражение:
event -> System.out.println(event)
можно заменить на:
System.out::println
другой пример:
(x,y)->x.compareToIgnoreCase(y)
можно заменить на:
String::compareToIgnoreCase
Уведомление:После имени метода не может быть никаких параметров!можно записать какSystem.out::println
, но не может быть записано какSystem.out::println(“hello”)
Если вы можете получить этот параметр этого экземпляра, вы можете использовать его напрямуюметод this::instanceДля доступа к указанному методу родительского класса используйтеsuper::метод экземпляраполучить доступ.
Ниже приведен пример:
public class Greeter {
public void greet() {
String lowcaseStr = changeWords("Hello,World", this::lowercase);
System.out.println(lowcaseStr);
}
public String lowercase(String word) {
return word.toLowerCase();
}
public String changeWords(String words, UnaryOperator<String> func) {
return func.apply(words);
}
}
class ConcurrentGreeter extends Greeter {
public void greet() {
Thread thread = new Thread(super::greet);
thread.start();
}
public static void main(String[] args) {
new ConcurrentGreeter().greet();
}
}
Ссылка на конструктор
Ссылки на конструктор аналогичны ссылкам на методы, за исключением того, что функциональный интерфейс возвращает экземпляр объекта или массив. Синтаксис ссылки на конструктор следующий:
- класс:: новый
- массив :: новый
Например:
List<String> labels = Arrays.asList("button1", "button2");
Stream<Button> stream = labels.stream().map(Button::new);
List<Button> buttons = stream.collect(Collectors.toList());
один из нихlabels.stream().map(Button::new)
эквивалентноlabels.stream().map(label->new Button(label))
Другой пример ссылки на конструктор для типа массива:
Button[] buttons = stream.toArray(Button[]::new);
Преобразуйте Stream непосредственно в тип массива, где Button[]::new используется для указания типа массива.
переменная область видимости
Сначала посмотрите на кусок кода:
public void repeatMsg(String text, int count) {
Runnable r = () -> {
for (int i = 0; i < count; i++) {
System.out.println(text);
Thread.yield();
}
};
}
Лямбда-выражение обычно состоит из следующих трех частей:
- параметр
- выражение
- свободная переменная
Параметры и выражения просты для понимания. Что такое свободные переменные? Это внешняя переменная, на которую ссылается лямбда-выражение, например переменные text и count в приведенном выше примере.
Если вы знакомы с функциональным программированием, вы обнаружите, что лямбда-выражения на самом деле "Закрытие"(закрытие). Просто Java 8 не назвала это имя. Для свободных переменных, если для лямбда-выражения требуется ссылка, модификация не допускается.
Фактически, в анонимном внутреннем классе в Java, если вы хотите сослаться на внешнюю переменную, эту переменную необходимо объявить final Хотя свободную переменную лямбда-выражения не нужно объявлять final, это также не допускается. быть изменены.
Например следующий код:
public void repeatMsg(String text, int count) {
Runnable r = () -> {
while (count > 0) {
count--; // 错误,不能修改外部变量的值
System.out.println(text);
}
};
}
Кроме того, нельзя объявлять параметр или локальную переменную с тем же именем, что и локальная переменная в лямбда-выражении. Например следующий код:
Path first = Paths.get("/usr/bin");
Comparator<String> comp = (first, second) -> Integer.compare(first.length(), second.length());
// 错误,变量first已经被定义
Метод по умолчанию в интерфейсе
Давайте поговорим о том, почему метод по умолчанию добавлен в интерфейс Java8.
Например, разработчики интерфейса Collection добавили метод forEach() для обхода коллекций, который можно использовать для более точного обхода коллекций. Например:
list.forEach(System.out::println());
Однако, если в интерфейс добавляется новый метод, то по традиционному методу пользовательский класс реализации интерфейса Collection должен реализовывать метод forEach(), что неприемлемо для большинства существующих реализаций.
Поэтому разработчики Java8 придумали этот метод: добавить в интерфейс новый тип метода, называемый методом по умолчанию, который может обеспечить реализацию метода по умолчанию, так что, если класс реализации не реализует метод, реализация по умолчанию метод можно использовать по умолчанию.
Пример использования:
public interface Person {
long getId();
default String getName() {
return "jack";
}
}
Добавление метода по умолчанию, вы можетеЗамените предыдущий классический интерфейс и дизайн абстрактного классаунифицированные абстрактные методы и реализации по умолчанию определяются в интерфейсе. Вероятно, это умение было украдено у Scala's Trait.
статический метод в интерфейсе
В дополнение к методам по умолчанию Java8 также поддерживает определение в интерфейсахстатический методи реализация.
Например, до Java 8 для интерфейса Path обычно определяется инструментальный класс Paths, а вспомогательные методы интерфейса реализуются через статические методы.
В интерфейсе легко иметь статические методы, и они унифицированы в одном интерфейсе! Хотя это, кажется, разрушает первоначальные дизайнерские задумки интерфейса.
public interface Path{
public static Path get(String first, String... more) {
return FileSystem.getDefault().getPath(first, more);
}
}
Таким образом, класс Paths не имеет смысла ~
резюме
После использования лямбда-выражений избыточное избыточноекод шаблона, чтобы больше сосредоточиться на бизнес-логике, а не дублировать кучу重复代码
, если вы не работаете в компании, которая измеряет усилия в строках кода, что вы думаете?