Статьи

Лямбда-выражения и Stream API: основные примеры

Этот пост содержит список основных лямбда-выражений и примеров Stream API, которые я использовал в презентации с живым кодированием, которую я дал в июне 2014 года в группе пользователей Java — Politechnica Gedanensis (Технический университет Гданьска) и в Гойелло .

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

Синтаксис

Самый распространенный пример:

1
2
3
4
Runnable runnable = () -> System.out.println("Hello!");
Thread t = new Thread(runnable);
t.start();
t.join();

Можно написать это по-другому:

1
2
3
Thread t = new Thread(() -> System.out.println("Hello!"));
t.start();
t.join();

Как насчет аргументов?

1
Comparator<String> stringComparator = (s1, s2) -> s1.compareTo(s2);

И расширившись до полного выражения:

1
2
3
4
Comparator<String> stringComparator = (String s1, String s2) -> {
    System.out.println("Comparing...");
    return s1.compareTo(s2);
};

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

Лямбда-выражения позволяют более компактно выражать экземпляры классов с одним методом. Классы с одним методом называются функциональными интерфейсами и могут быть аннотированы с помощью @FunctionalInterface :

1
2
3
4
5
6
7
@FunctionalInterface
public interface MyFunctionalInterface<T> {
    boolean test(T t);
}
 
// Usage
MyFunctionalInterface<String> l = s -> s.startsWith("A");

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

Ссылки на методы — это компактные, легко читаемые лямбда-выражения для методов, которые уже имеют имя. Давайте посмотрим на этот простой пример:

01
02
03
04
05
06
07
08
09
10
public class Sample {
 
    public static void main(String[] args) {
       Runnable runnable = Sample::run;
    }
 
    private static void run() {
        System.out.println("Hello!");
    }
}

Другой пример:

1
2
3
4
5
6
7
8
public static void main(String[] args) {
    Sample sample = new Sample();
    Comparator<String> stringLengthComparator = sample::compareLength;
}
 
private int compareLength(String s1, String s2) {
    return s1.length() - s2.length();
}

Stream API — основы

Поток — это последовательность элементов, поддерживающая последовательные и параллельные массовые операции.

Перебор списка

1
2
3
4
List<String> list = Arrays.asList("one", "two", "three", "four", "five", "six");
 
list.stream()
        .forEach(s -> System.out.println(s));

фильтрация

Java 8 представила методы по умолчанию в интерфейсах. Они удобны в Stream API:

1
2
3
4
5
6
Predicate<String> lowerThanOrEqualToFour = s -> s.length() <= 4;
Predicate<String> greaterThanOrEqualToThree = s -> s.length() >= 3;
 
list.stream()
        .filter(lowerThanOrEqualToFour.and(greaterThanOrEqualToThree))
        .forEach(s -> System.out.println(s));

Сортировка

1
2
3
4
5
6
7
8
9
Predicate<String> lowerThanOrEqualToFour = s -> s.length() <= 4;
Predicate<String> greaterThanOrEqualToThree = s -> s.length() >= 3;
Comparator<String> byLastLetter = (s1, s2) -> s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1);
Comparator<String> byLength = (s1, s2) -> s1.length() - s2.length();
 
list.stream()
        .filter(lowerThanOrEqualToFour.and(greaterThanOrEqualToThree))
        .sorted(byLastLetter.thenComparing(byLength))
        .forEach(s -> System.out.println(s));

В приведенном выше примере используется метод по умолчанию and java.util.function.Predicate . Методы по умолчанию (и статические) являются новыми для интерфейсов в Java 8.

предел

01
02
03
04
05
06
07
08
09
10
Predicate<String> lowerThanOrEqualToFour = s -> s.length() <= 4;
Predicate<String> greaterThanOrEqualToThree = s -> s.length() >= 3;
Comparator<String> byLastLetter = (s1, s2) -> s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1);
Comparator<String> byLength = (s1, s2) -> s1.length() - s2.length();
 
list.stream()
        .filter(lowerThanOrEqualToFour.and(greaterThanOrEqualToThree))
        .sorted(byLastLetter.thenComparing(byLength))
        .limit(4)
        .forEach(s -> System.out.println(s));

Собрать в список

01
02
03
04
05
06
07
08
09
10
Predicate<String> lowerThanOrEqualToFour = s -> s.length() <= 4;
Predicate<String> greaterThanOrEqualToThree = s -> s.length() >= 3;
Comparator<String> byLastLetter = (s1, s2) -> s1.charAt(s1.length() - 1) - s2.charAt(s2.length() - 1);
Comparator<String> byLength = (s1, s2) -> s1.length() - s2.length();
 
List<String> result = list.stream()
        .filter(lowerThanOrEqualToFour.and(greaterThanOrEqualToThree))
        .sorted(byLastLetter.thenComparing(byLength))
        .limit(4)
        .collect(Collectors.toList());

Параллельная обработка

Я использовал довольно распространенный пример итерации по списку файлов:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
public static void main(String[] args) {
    File[] files = new File("c:/windows").listFiles();
    Stream.of(files)
            .parallel()
            .forEach(Sample::process);
}
 
private static void process(File file) {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
    }
 
    System.out.println("Processing -> " + file);
}

Обратите внимание, что при показе примеров я объяснил некоторые известные недостатки параллельной обработки потоков.

Stream API — больше примеров

картографирование

Переберите файлы в каталоге и верните объект FileSize :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
class FileSize {
 
    private final File file;
    private final Long size;
 
    FileSize(File file, Long size) {
        this.file = file;
        this.size = size;
    }
 
    File getFile() {
        return file;
    }
 
    Long getSize() {
        return size;
    }
 
    String getName() {
        return getFile().getName();
    }
 
    String getFirstLetter() {
        return getName().substring(0, 1);
    }
 
    @Override
    public String toString() {
        return Objects.toStringHelper(this)
                .add("file", file)
                .add("size", size)
                .toString();
    }
}

Финальный код отображения:

1
2
3
4
File[] files = new File("c:/windows").listFiles();
List<FileSize> result = Stream.of(files)
        .map(FileSize::new)
        .collect(Collectors.toList());

Группировка

Группировать объект FileSize по первой букве имени файла:

1
2
3
Map<String, List<FileSize>> result = Stream.of(files)
        .map(FileSize::new)
        .collect(Collectors.groupingBy(FileSize::getFirstLetter));

уменьшить

Получите самый большой / самый маленький файл в каталоге:

1
2
3
Optional<FileSize> filesize = Stream.of(files)
        .map(FileSize::new)
        .reduce((fs1, fs2) -> fs1.getSize() > fs2.getSize() ? fs1 : fs2);

Если вам не нужен объект FileSize , а только число:

1
2
3
4
OptionalLong max = Stream.of(files)
        .map(FileSize::new)
        .mapToLong(fs -> fs.getSize())
        .max();