Java. Поняття кінцевої та проміжної операції

Поняття кінцевої та проміжної операції. Відмінності. Приклади. Методи створення потоку даних stream(), parallelStream()


Зміст


Пошук на інших ресурсах:




1. Кінцева та проміжна операції. Визначення

Під операцією мається на увазі виклик якогось методу, що обробляє потік. При обробці потоку виникають поняття кінцевої операції та проміжної операції.

Якщо у визначенні методу описується термін “кінцева операція“, то це означає що переглянуто (оброблено) увесь потік з початку до кінця і отримано кінцевий результат. Після цього даний потік вважається спожитим і його використовувати не можна. Якщо спробувати обробити спожитий потік після виконання кінцевої операції (методу), то система згенерує виключну ситуацію.

Якщо в описі операції (методу) вказано “проміжна операція“, то це означає, що створюється (продукується) новий потік даних, який може оброблятись далі в конвеєрному режимі. Конвеєрний режим передбачає подальше використання проміжних або кінцевих операцій над новоствореним потоком. У цьому випадку кінцева операція викликається останньою.

 

2. Кінцева операція. Приклад

У прикладі демонструється виконання кінцевих операцій.

Умова задачі. Обчислити мінімальне та максимальне значення у наборі випадкових чисел типу Double.

Текст демонстраційної програми наступний.

import java.util.ArrayList;
import java.util.Optional;
import java.util.stream.*;

public class StreamAPI {

  public static void main(String[] args) {
    // Конвертувати в масив Double[] потік чисел
    // 1. Оголосити набір чисел у вигляді колекції
    ArrayList<Double> AL = new ArrayList<Double>();

    // 2. Заповнити набір чисел випадковими значеннями
    for (int i=0; i<10; i++)
      AL.add((Math.random()*10));

    // 3. Створити потік чисел
    Stream<Double> stream = AL.stream();

    // 4. Знайти максимальне значення в масиві
    Optional<Double> max = stream.max(Double::compare);
    System.out.println("max = " + max.get());

    // 5. Спроба виклику ще однієї операції - пошук мінімуму
    stream = AL.stream(); // Обов'язково потрібно створити новий потік
    Optional<Double> min = stream.min(Double::compare);
    System.out.println("min = " + min.get());
  }
}

У вищенаведеному коді спочатку виконується пошук максимуму, потім виконується пошук мінімуму. Операція пошуку максимуму

stream.max(Double::compare);

є кінцевою. Це означає, що після її виконання потік вважається спожитим. Спожитий потік неможливо далі обробляти. Якщо після пошуку максимуму забрати рядок

stream = AL.stream();

то після запуску на виконання програма згенерує виключення IllegalStateException.

Результат роботи програми

max = 8.627680677435304
min = 1.4456409633880085

 

3. Проміжна операція. Приклад

У прикладі демонструється виконання проміжної операції, яка сортує потік чисел у порядку спадання.

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

public class StreamAPI {

  public static void main(String[] args) {
    // Сортування потоку чисел у порядку спадання значень
    // 1. Створити набір чисел
    ArrayList<Integer> AL = new ArrayList<Integer>();

    for (int i=0; i<10; i++)
      AL.add((int)(Math.random()*10));
    System.out.println("AL = " + AL);

    // 2. Створити потік чисел на основі колекції AL
    Stream<Integer> stream = AL.stream();

    // 3. Викликати проміжну операцію сортування - створюється новий потік.
    // 3.1. Створити метод порівняння
    Comparator<Integer> comparator = (a, b) -> b-a;

    // 3.2. Створити новий посортований потік - це є проміжна операція,
    //      після неї потік можна далі обробляти
    Stream<Integer> sortStream = stream.sorted(comparator);

    // 3.3. Вивести новий потік
    System.out.print("sorted AL = ");

    // 3.3.1. Створити дію, яка виводить елемент потоку на екран
    Consumer<Integer> action;
    action = (n) -> {
      System.out.print(n + " ");
    };

    // Передати дію action в метод forEach() для поелементної обробки
    sortStream.forEach(action); // forEach() - кінцева операція
  }
}

У вищенаведеному коді створюється потік чисел stream з колекції AL. Отриманий потік сортується проміжною операцією sorted(). Метод sorted() утворює новий посортований потік.

Stream<Integer> sortStream = stream.sorted(comparator);

Оскільки sorted() є проміжною операцією, то новостворений потік далі можна обробляти. У нашому випадку здійснюється поелементний вивід усього потоку з допомогою методу forEach() як показано нижче

sortStream.forEach(action); // forEach() - кінцева операція

Метод forEach() є кінцевою операцією. Це означає, що потік sortStream спожито. Щоб використати потік заново, його потрібно відновити з джерела – набору чисел AL.

Після виконання програма видасть наступний результат

AL = [5, 4, 0, 5, 2, 9, 1, 3, 0, 2]
sorted AL = 9 5 5 4 3 2 2 1 0 0

 

4. Формування нових потоків даних з допомогою конвеєру. Приклад

Умова задачі. Задано потік чисел. Знайти мінімальне з чисел потоку, які більше 5 і менше 20.

Розв’язок. Для розв’язку задачі потрібно використати підхід конвеєра для модифікації потоку. Використовуються дві операції (методи):

  • операція (метод) filter() – проміжна операція;
  • операція (метод) min() – кінцева операція.

Текст розв’язку задачі наступний

import java.util.*;
import java.util.function.*;
import java.util.stream.*;

public class StreamAPI {

  public static void main(String[] args) {
    // Задача. Задано потік чисел. Знайти мінімальне з чисел потоку, які
    //         більше 5 і менше 20.
    // 1. Створити набір чисел
    ArrayList<Integer> AL = new ArrayList<Integer>();

    AL.add(15);
    AL.add(13);
    AL.add(7);
    AL.add(1);
    AL.add(23);
    AL.add(6);
    AL.add(3);
    AL.add(11);
    AL.add(22);
    AL.add(2);

    // 2. Створити потік чисел на основі колекції AL
    Stream<Integer> stream = AL.stream();
    System.out.println("AL = " + AL);

    // 3. Викликати проміжну операцію фільтрування - створюється новий потік.
    // 3.1. Створити метод порівняння - числа, які більше за 5
    Predicate<Integer> predicate = (a) -> a>5; // предикат (умова)

    // 3.2. Створити новий відфільтрований потік - це є проміжна операція,
    //      після неї потік можна далі обробляти
    Stream<Integer> filteredStream = stream.filter(predicate);

    // 4. Викликати ще одну проміжну операцію фільтрування - створюється новий потік
    //    Цього разу фільтруються числа, які менше 20.
    predicate = (a) -> a<20;

    // 5. Відфільтрувати той самий потік ще раз
    filteredStream = filteredStream.filter(predicate); // це знову проміжна операція

    // 6. Знайти мінімальне значення у новоствореному потоці
    Optional<Integer> min = filteredStream.min(Integer::compare); // це є кінцева операція

    // 7. Вивести мінімальне значення на екран
    System.out.println("min = " + min.get());
  }
}

Після виконання програма видасть наступний результат

AL = [15, 13, 7, 1, 23, 6, 3, 11, 22, 2]
min = 6

 

5. Приклади отримання (відкриття) потоку даних. Методи stream(), parallelStream()

Приклад реалізує отримання потоку даних з різних видів колекцій. Для отримання послідовного потоку використовується метод stream(). Для отримання паралельного потоку використовується метод parallelStream().

import java.util.*;
import java.util.stream.*;

public class StreamAPI {

  public static void main(String[] args) {
    // Задача. Задано набір чисел. З цього набору створити потік
    // 1. Створити потік даних з колекції типу ArrayList<T>
    // 1.1. Створити колекцію чисел
    ArrayList<Integer> AL = new ArrayList<Integer>();

    AL.add(15);     AL.add(13);     AL.add(7);     AL.add(1);     AL.add(23);
    AL.add(6);     AL.add(3);     AL.add(11);     AL.add(22);     AL.add(2);

    // 1.2. Створити послідовний потік даних на основі колекції AL
    Stream<Integer> stream = AL.stream();

    // 1.3. Створити паралельний потік даних з колекції AL
    Stream<Integer> parallelStream = AL.parallelStream();

    // 2. Створити потік даних з масиву цілих чисел
    int[] AI = { 2, 5, 4, 8, -1, 3, 9 };
    IntStream streamInt = Arrays.stream(AI); // послідовний потік
    IntStream parallelStreamInt = streamInt.parallel(); // паралельний потік

    // 3. Створити потік даних з масиву дійсних чисел
    double[] AD = { 0.5, -1.8, 3.77, 4.23, 21.7, -10.8 };
    DoubleStream streamDouble = Arrays.stream(AD); // послідовний потік до даного
    DoubleStream parallelStreamDouble = streamDouble.parallel(); // паралельний потік

    // 4. Створити потік даних з масиву типу long
    Long[] ALong = { 282039l, 3239290l, 23902309l, 72083289L };
    Stream<Long> streamLong = Arrays.stream(ALong); // послідовний потік
    Stream<Long> parallelStreamLong = streamLong.parallel(); // паралельний потік
  }
}

 


Зв’язані теми