Статьи

Лямбдас: приближаемся к Java 8 рядом с тобой!

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

Лямбда-выражение — это тип анонимной функции, которая может быть встроена в метод внутри строки и использоваться везде, где используются выражения. Иногда вы можете найти их упоминаемыми как замыкания, хотя есть некоторые оговорки к этой ссылке, которые я объясню ниже. Как и обычный метод Java, он имеет параметры и тело, которое может быть выполнено. Одна из замечательных новых функций в Java 8 — это лямбда-выражения, которые определяются как часть JSR 335 .

Добавление лямбда-выражений позволит Java легче поддерживать методы функционального программирования и функции более высокого порядка. Это не значит, что внезапно он внезапно станет экзотическим и эзотерическим языком, таким как Haskell, но он будет двигаться в том же направлении, что и языки, такие как C #, Ruby или Python. То есть, предлагая частичную поддержку функционального стиля, где это имеет смысл, но все же позволяя вам использовать императивные методы, такие как изменяемые переменные.

Основной синтаксис

Вот лямбда-выражение, которое увеличивает число на 1:

1
x -> x + 1

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

1
2
3
4
5
6
// Explicit Types
    (Integer x) -> x + 1
    // Multiple Arguments
    (Integer x1, Integer x2) -> x1 + 1
    // Annotations:
    (Integer x1, @SuppressWarnings("unused") Integer x2) -> x1 + 1

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

1
2
3
4
5
(x) -> {
    x += 5;
    System.out.println(x);
    return x;
};

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

1
2
3
4
5
6
7
8
9
// a method:
public static void useFunc(IntFun f) {
    System.out.println(f.apply(2));
}
 
// in code
IntFun inc = x -> x + 1;
useFunc(inc);
useFunc(x -> x + 1);

Типы?

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

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

01
02
03
04
05
06
07
08
09
10
11
interface Runnable { void run(); }
 
interface Function { public R apply(A a); }
 
interface IntFun extends Function {}
 
// equals is defined by java.lang.Object, so this only has one abstract method
interface Comparator {
    boolean equals(Object obj);
    int compare(T o1, T o2);
}

Следовательно, любой API, который в настоящее время записывается / пишется, может быть использован lambdas, гарантируя, что он использует интерфейсы, которые поддерживают эти ограничения. Одним из примеров API, который уже поощряет ограниченный стиль функционального программирования, являются библиотеки Guava .

Более интересные примеры

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

1
2
3
4
5
6
Collection result = transform(newArrayList(1, 2, 3), new Function() {
    @Override
    public Integer apply(Integer x) {
        return x + 1;
    }
});

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

1
Collection result = transform(newArrayList(1, 2, 3),x -> x + 1);

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

1
2
3
4
5
6
7
8
Collection threeAndFive = filter(newArrayList(1, 3, 5), new Predicate() {
    @Override
    public boolean apply(Integer x) {
        return x > 2;
    }
});
 
Collection threeAndFiveByLambda = filter(newArrayList(1, 3, 5),x -> x > 2);

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

Конечно, у вас может быть существующая кодовая база со многими уже определенными методами, которую вы хотите использовать вместо лямбд. Это также возможно в настоящее время в Java, используя анонимные внутренние классы, но столь же громоздко. Помимо предоставления лямбда-выражений, JSR 335 предлагает некоторый синтаксический сахар для упрощения этого процесса. Вы можете использовать символ «::» в качестве ссылки на метод для существующих методов, которые вы написали. Вот предыдущий пример, но с использованием ссылки на метод:

1
2
3
4
5
6
7
// Existing method
public static boolean greaterThanTwo(Integer x) {
    return x > 2;
}
 
// prints [3, 5]
System.out.println(filter(newArrayList(1,3,5),LambdasExample::greaterThanTwo));

Они действительно закрывают?

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

1
2
3
int value = 5;
 
IntFun addValue = x -> x + value;

В этом примере кода функция ‘addValue’ может ссылаться на переменную с именем ‘value’ в окружающей области видимости, хотя здесь есть предостережение. Если вы ранее написали анонимные внутренние классы, то вспомните, что они могут ссылаться только на окружающие переменные, помеченные как окончательные, то есть им не назначенные. Ограничение здесь аналогично, за исключением того, что оно было обобщено, чтобы быть эффективным окончательным . Проще говоря, вы можете ссылаться на переменные, которые не присваиваются более одного раза. Принцип заключается в том, что переменная является окончательной, если она либо помечена как конечная, либо может быть помечена как конечная без внесения ошибки компилятора.

Попытка и ссылки

Часть раннего чернового варианта спецификации лямбды уже размещена в сети, и если вы хотите узнать все подробности происходящего, это может быть интересно для вас. Уже есть бинарные снимки, если вы хотите написать лямбда-код. Они обновляются очень часто, и если вы хотите взглянуть на внутренности лямбда- кода, то вы можете попробовать исходную сборку . Мои единственные предостережения относительно написания кода с использованием двоичного кода — это то, что он все еще несколько глючит, поскольку он находится в черновом выпуске, а также использует оператор «#» вместо «::» в качестве синтаксиса ссылки на метод.

Резюме

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

Ссылка: Lambdas: приближаемся к Java 8 рядом с вами! от нашего партнера JCG Ричарда Уорбертона в блоге Insightful Logic .