betacode

Руководство Java Stream

  1. Stream
  2. Stream.filter(Predicate)
  3. Stream.sorted(Comparator)
  4. Stream.map(Function)
  5. Stream.flatMap(Function)

1. Stream

Java 8 вводит новую концепцию под названием Stream (поток). Когда вы впервые читаете о Stream API, вы можете быть сбиты с толку, потому что его название похоже на InputStream и OutputStream. Но Java 8 Stream - это нечто совершенно другое. Stream - это монада (monad), поэтому он играет важную роль в внедрении функционального программирования (functional programming) в Java.
Прежде чем начать эту статью, я рекомендую вам ознакомиться с functional interface и некоторыми распространенными functional interface, такими как: Supplier, Consumer, Predicate. Ниже приведены мои статьи:
В функциональном программировании (functional programming), монада (monad) -это структура, представляющая собой вычисление (computation), требующее последовательности связанных шагов. Для простоты смотрите пример монады ниже:
List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");

myList
      .stream() // (1)  return a Stream
      .filter(s -> s.startsWith("c")) // (2)  return a new Stream
      .map(String::toUpperCase)  // (3)  return a new Stream
      .sorted()   // (4)  return a new Stream
      .forEach(System.out::println); // (5)
Output:
C1
C2
  • Создать Stream из объекта List.
  • Создать новый Stream из предыдущего Stream, который включает только элементы, начинающиеся с буквы "с".
  • Создать новый Stream из предыдущего Stream со всеми элементами, преобразованными в заглавные (upercase).
  • Создать новый Stream из предыдущего Stream, отсортировав элементы.
  • Распечатать элементы последнего Stream.
В предыдущем примере, шаги (2) - (4) являются промежуточными операциями (intermediate operation), поскольку они возвращают объект Stream. Таким образом, вы можете вызвать другой метод Stream без необходимости заканчивать его точкой с запятой.
Терминальная операция (terminal operation) - это метод, который возвращает void или возвращает другой тип с Stream. В предыдущем примере шаг 5 является терминальной операцией, поскольку метод Stream.forEach возвращает void.
Ниже приведены характеристики и преимущества Java 8 Stream:
  • Никакого хранения. Stream - это не структура данных, а только представление источника данных (который может быть массивом, списком илиI/O Channel и т.д).
  • Stream функциональен по своей природе. Любые изменения с Stream не изменят источники данных. Например, фильтрация Stream не приведет к удалению каких-либо элементов, а создаст новый Stream, включающий отфильтрованные элементы.
  • Ленивое исполнение. Операции над Stream будут выполняться не сразу. Они будут выполняться только тогда, когда пользователям действительно нужны результаты.
  • Расходуемые (Consumable). Доступ к элементам Stream существляется только один раз за время существования Stream. После утверждения, Stream становится недействительным, как и Iterator. Вы должны восстановить новый Stream, если хотите снова посмотреть Stream.
Смотрите иллюстрацию ниже, чтобы лучше понять, как работает Stream.
  • Создайте stream из коллекции (collection).
  • Фильтровать цвета, отличные от красного.
  • Нарисовать треугольники розовой краской.
  • Фильтровать формы, которые не являются квадратными.
  • Вычислить общую площадь.
Класс Employee участвует в нескольких примерах из этой статьи:
Employee.java
package org.o7planning.stream.ex;

public class Employee {

    private String name;
    private float salary;
    private String gender; // "M", "F"

    public Employee(String name, float salary, String gender) {
        this.name = name;
        this.salary = salary;
        this.gender = gender;
    }

    public String getName() {
        return name;
    }

    public float getSalary() {
        return salary;
    }

    public String getGender() {
        return gender;
    }

    public boolean isFemale() {
        return "F".equals(this.getGender());
    }
}

2. Stream.filter(Predicate)

Возвращает Stream, состоящий из элементов этого Stream, соответствующих заданному Predicate.
Stream<T> filter(Predicate<? super T> predicate)
Например: Из списка сотрудников (Employee) нужно распечатать список сотрудниц с зарплатой более 2500.
Stream_filter_ex1.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

public class Stream_filter_ex1 {

    public static void main(String[] args) {

        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);
        
        // Employee is Female and salary > 2500
        Predicate<Employee> predicate = e -> e.isFemale() && e.getSalary() > 2500;

        employees //
             .stream() //
             .filter(predicate) //
             .forEach(e -> System.out.println(e.getName()+ " : " + e.getSalary()));
    }
}
Output:
Mary T. : 5000.0
Sophia B. : 7000.0
Если нестатический метод (non-static method), не имеет параметров и возвращает значение boolean, то его ссылка считается Predicate. (См. объяснение в моей статье о Java Predicate).
Например: Создать Predicate из ссылки метода (method reference):
Predicate<Employee> p = Employee::isFemale;
 
// Same as:
 
Predicate<Employee> p = employee -> employee.isFemale();
Stream_filter_ex2.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;

public class Stream_filter_ex2 {

    public static void main(String[] args) {
        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);

        employees //
             .stream() //
             .filter(Employee::isFemale) //
             .forEach(e -> System.out.println(e.getName()+ " : " + e.getSalary()));
    }
}
Output:
Sarah M. : 2000.0
Mary T. : 5000.0
Sophia B. : 7000.0

3. Stream.sorted(Comparator)

Возвращает Stream, состоящий из элементов этого потока, отсортированных в соответствии с предоставленным Comparator.
Stream<T> sorted(Comparator<? super T> comparator)
  • Руководство Java Comparator
Например: Сортировка сотрудников в порядке возрастания заработной платы:
Stream_sort_ex1.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;

public class Stream_sort_ex1 {

    public static void main(String[] args) {
        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);

        employees //
             .stream() //
             .sorted (
                 (e1,e2) -> (int) (e1.getSalary() - e2.getSalary())
              ) //
             .forEach(e -> System.out.println(e.getSalary() + " : " + e.getName()));
    }
}
Output:
1500.0 : John P.
1700.0 : Charles B.
2000.0 : Sarah M.
5000.0 : Mary T.
7000.0 : Sophia B.
Например: Сортировка сотрудника по полу (Gender) и зарплате:
Stream_sort_ex2.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;

public class Stream_sort_ex2 {

    public static void main(String[] args) {
        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);

        employees //
             .stream() //
             .sorted (
                 (e1,e2) -> {
                     int v = e1.getGender().compareTo(e2.getGender());
                     if(v == 0) {
                         v = (int) (e1.getSalary() - e2.getSalary());
                     }
                     return v;
                 }
              ) //
             .forEach(e -> System.out.println(e.getGender()+ " : "+ e.getSalary() + " : " + e.getName()));
    }
}
Output:
F : 2000.0 : Sarah M.
F : 5000.0 : Mary T.
F : 7000.0 : Sophia B.
M : 1500.0 : John P.
M : 1700.0 : Charles B.

4. Stream.map(Function)

Возвращает новый Stream, состоящий из результатов применения данной Function к элементам этого Stream.
<R> Stream<R> map(Function<? super T,? extends R> mapper)
Например: преобразует список String в заглавные (uppercase).
Stream_map_ex1.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Stream_map_ex1 {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b", "c", "d", "e");
        
        List<String> newList = list //
                    .stream() // a Stream
                    .map(s -> s.toUpperCase()) // a new Stream
                    .collect(Collectors.toList()); // Stream => List

        System.out.println(list); // [a, b, c, d, e]
        System.out.println(newList); // [A, B, C, D, E]
    }
}
Если нестатический метод (non-static method), не имеет параметров и возвращает значение, то его ссылка рассматривается как Function. (См. Более подробное объяснение в моей статье о Java Function).
// Create a Function from a method reference:
Function<String, String> f1 = String::toUpperCase;
// Same as:
Function<String, String> f2 = s -> s.toUpperCase();
Stream_map_ex2.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class Stream_map_ex2 {

    public static void main(String[] args) {
        List<String> list = Arrays.asList("a", "b", "c", "d", "e");
        
        List<String> newList = list //
                    .stream() // a Stream
                    .map(String::toUpperCase) // a new Stream
                    .collect(Collectors.toList()); // Stream => List  

        System.out.println(list); // [a, b, c, d, e]
        System.out.println(newList); // [A, B, C, D, E]
    }
}
Например: Двойная зарплата для каждого сотрудника в списке:
Stream_map_ex3.java
package org.o7planning.stream.ex;

import java.util.Arrays;
import java.util.List;

public class Stream_map_ex3 {

    public static void main(String[] args) {

        Employee john = new Employee("John P.", 1500, "M");
        Employee sarah = new Employee("Sarah M.", 2000, "F");
        Employee charles = new Employee("Charles B.", 1700, "M");
        Employee mary = new Employee("Mary T.", 5000, "F");
        Employee sophia = new Employee("Sophia B.", 7000, "F");

        List<Employee> employees = Arrays.asList(john, sarah, charles, mary, sophia);
        
        employees //
             .stream() // a Stream.
             .map((e) -> new Employee(e.getName(), e.getSalary()* 2, e.getGender())) // a new Stream.
             .forEach(c -> System.out.println(c.getName()+ " : " + c.getSalary()));
    }  
}
Output:
John P. : 3000.0
Sarah M. : 4000.0
Charles B. : 3400.0
Mary T. : 10000.0
Sophia B. : 14000.0

5. Stream.flatMap(Function)

public class Stream<T> {

    <R> Stream<R> flatMap​(Function<? super T,​? extends Stream<? extends R>> mapper);
    
    // .....
}
Метод Stream.flatMap довольно полезен и есть многое рассказать, поэтому я разделил его в отдельной статье:
  • Руководство Java Stream.flatMap

Java Basic

Show More