betacode

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

View more Tutorials:

Сайт бесплатного изучения языков:
Следуйте за нами на нашей фан-странице, чтобы получать уведомления каждый раз, когда появляются новые статьи. Facebook

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
  1. Создать Stream из объекта List.
  2. Создать новый Stream из предыдущего Stream, который включает только элементы, начинающиеся с буквы "с".
  3. Создать новый Stream из предыдущего Stream со всеми элементами, преобразованными в заглавные (upercase).
  4. Создать новый Stream из предыдущего Stream, отсортировав элементы.
  5. Распечатать элементы последнего 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.
  1. Создайте stream из коллекции (collection).
  2. Фильтровать цвета, отличные от красного.
  3. Нарисовать треугольники розовой краской.
  4. Фильтровать формы, которые не являются квадратными. 
  5. Вычислить общую площадь.
Класс 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)
  • TODO Link?
Например: Сортировка сотрудников в порядке возрастания заработной платы:
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 довольно полезен и есть многое рассказать, поэтому я разделил его в отдельной статье:
  • TODO Link?

View more Tutorials: