Java. Інтерфейс StreamAPI. Кінцеві методи. Приклади використання

Інтерфейс StreamAPI. Кінцеві методи. Приклади використання. Методи collect(), count(), forEach(), max(), min(), reduce(), toArray()


Зміст


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




1. Метод collect(). Накопичення елементів у контейнері. Приклад

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

Загальна форма методу наступна:

<R, A> R collect(Collector<? super T, A, R> collector)

тут

  • R – тип результуючого контейнеру;
  • T – тип елементів у вихідному (викликаючому) потоці даних;
  • A – внутрішній накопичувальний тип;
  • collector – функція накопичення, яка представляється лямбда-виразом. Функція визначає порядок виконання процесу накопичення.

Метод collect() є кінцевою операцією (terminal operation).

Приклад. У прикладі для потоку чисел типу Double продемонстровано наступні накопичення елементів:

  • отримати з потоку даних множину типу set<Double>. Для реалізації цього використовується метод-колектор toSet() статичного класу Collectors пакету java.util.stream;
  • отримати з потоку даних список типу List<Double>. У цьому випадку використовуєтсься метод toList() статичного класу Collectors.

 

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

public class StreamAPI {

  public static void main(String[] args) {
    // Метод collect() - накопичення елементів у контейнері
    // 1. Створити набір чисел
    ArrayList<Double> AL = new ArrayList<Double>();
    AL.add(1.5);
    AL.add(2.8);
    AL.add(-2.3);
    AL.add(3.4);
    AL.add(1.1);

    // 2. Отримати потік даних зі списку типу ArrayList<Double>
    Stream<Double> stream = AL.stream();

    // 3. Виконати зворотню операцію - отримати множину типу Set<Double>
    Set<Double> set;
    set = stream.collect(Collectors.toSet()); // метод collect()

    // 4. Вивести множину set на екран
    System.out.print("set = { ");
    for (Double t : set)
      System.out.print(t + " ");
    System.out.println("}");

    // 5. Отримати з множини set новий потік даних
    Stream<Double> stream2 = set.stream();

    // 6. Отримати з нового потоку даних новий список типу List<Double>
    List<Double> AL2 = stream2.collect(Collectors.toList());

    // 7. Вивести новий список на екран
    System.out.print("AL2 = [ ");
    for (Double t : AL2)
      System.out.print(t + " ");
    System.out.println("]");
  }
}

Результат виконання програми

set = { 2.8 -2.3 1.5 3.4 1.1 }
AL2 = [ 2.8 -2.3 1.5 3.4 1.1 ]

 

2. Метод count(). Отримати кількість елементів у потоці. Приклад

Метод count() повертає кількість елементів у потоці. Загальна форма методу

long count()

Метод count() є кінцевою операцією.

Приклад використання методу count().

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

public class StreamAPI {

  public static void main(String[] args) {
    // Метод count() - отримати кількість елементів у потоці
    // 1. Створити набір чисел
    ArrayList<Double> AL = new ArrayList<Double>();
    AL.add(1.5);
    AL.add(2.8);
    AL.add(-2.3);
    AL.add(3.4);
    AL.add(1.1);

    // 2. Отримати потік даних
    Stream<Double> stream = AL.stream();

    // 3. Отримати кількість елементів
    long count = stream.count();
    System.out.println("count = " + count);
  }
}

Результат виконання програми

count = 5

 

3. Метод forEach(). Виконати дію над кожним елементом потоку. Приклад

Метод forEach() застосовується у випадках, коли над кожним елементом потоку потрібно виконати деяку дію (роботу). Наприклад, для зручного виведення значень елементів потоку на екран.

Загальна форма методу forEach() наступна:

void forEach(Consumer<? super T> action)

тут

  • Consumer<? super T> – тип, що є стандартним функціональним інтерфейсом. У функціональному інтерфейсі поточний тип елементів обмежується деяким типом T (? super T);
  • action – лямбда-вираз, що здійснює дію над окремим елементом потоку.

Метод forEach() є кінцевою операцією (terminal operation).

Приклад. У прикладі над кожним елементом потоку виконуються такі дії:

  • помножити кожен елемент потоку на 2;
  • вивести кожен елемент потоку на екран.

Для виконання дії метод forEach() отримує відповідний лямбда-вираз, що реалізує функціональний інтерфейс Consumer<Double>.

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

public class StreamAPI {

  public static void main(String[] args) {
    // Метод forEach() - виконати дію над кожним елементом потоку
    // 1. Створити набір чисел
    ArrayList<Double> AL = new ArrayList<Double>();
    AL.add(1.5);
    AL.add(2.8);
    AL.add(-2.3);
    AL.add(3.4);
    AL.add(1.1);

    // 2. Отримати потік даних
    Stream<Double> stream = AL.stream();

    // 3. Створити посилання на стандартний функціональний інтерфейс
    //    Consumer<T> та присвоїти йому лямбда-вираз
    Consumer<Double> action = (value) -> {
      value = value*2; // помножити кожен елемент потоку на 2
      System.out.print(value + " "); // вивести елемент на екран
    };

    // 4. Використати метод forEach() для виведення елементів потоку
    //    на екран
    stream.forEach(action);
  }
}

Результат виконання програми

3.0 5.6 -4.6 6.8 2.2

 

4. Метод max(). Повернути максимальний елементи в потоці даних. Приклад

Метод max() призначений для отримання елементу з максимальним значенням у потоці даних. Загальна форма методу наступна:

Optional<T> max(Comparator<? super T> comparator)

тут

  • T – тип елементів потоку даних;
  • Optional<T> – результат, що містить найбільше значення елементу в потоці даних. Щоб отримати елемент потрібно використати метод get();
  • comparator – функція порівняння двох значень типу T, яка представлена лямбда-виразом;
  • Comparator<? super T> – тип стандартного функціонального інтерфейсу Java, який використовується для порівняння двох значень узагальненого типу T.

Метод max() є кінцевою операцією.

Приклад. У прикладі оголошується клас Circle, який реалізує коло на координатній площині. Додатково формується потік даних типу Circle. Для вихідного потоку даних визначається коло в якого найбільша площа.

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

// Клас, що реалізує коло на координатній площині
class Circle {

  // Внутрішні поля класу
  private double x, y; // координата центру кола
  private double radius; // радіус кола

  // Конструктор
  public Circle(double x, double y, double radius) {
    this.x = x; this.y = y; this.radius = radius;
  }

  // Методи доступу
  public double getX() { return x; }
  public double getY() { return y; }
  public double getRadius() { return radius; }

  // Метод, що повертає площу круга
  public double Area() {
    return Math.PI*radius*radius;
  }
}

public class StreamAPI {

  public static void main(String[] args) {
    // Метод max() - визначає найбільше значення у потоці даних
    // 1. Створити набір даних типу Circle
    ArrayList<Circle> AL = new ArrayList<Circle>();
    AL.add(new Circle(2.0, 5.0, 3.3));
    AL.add(new Circle(2.5, 4.2, 3.1));
    AL.add(new Circle(3.1, 4.1, 2.3));
    AL.add(new Circle(4.0, 3.0, 4.3));
    AL.add(new Circle(3.0, 2.0, 1.3));

    // 2. Отримати потік даних типу Circle з набору AL
    Stream<Circle> stream = AL.stream();

    // 3. Оголосити компаратор - метод порівняння двох значень типу Circle
    Comparator<Circle> comparator;

    // у лямбда-виразі потрібно порівняти два екземпляри типу Circle
    comparator = (circle1, circle2) ->
                  (int)(circle1.Area()-circle2.Area());

    // 4. Знайти коло з найбільшою площею
    Optional<Circle> maxCircle = stream.max(comparator);

    // 5. Вивести результат
    System.out.println("maxCircle = (" + maxCircle.get().getX() + "; " +
    maxCircle.get().getY() + "), radius = " +
    maxCircle.get().getRadius());
    System.out.println("area = " + maxCircle.get().Area());
  }
}

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

maxCircle = (4.0; 3.0), radius = 4.3
area = 58.088048164875275

 

5. Метод min(). Повернути мінімальнй елемент у потоці даних. Приклад

Метод min() дозволяє отримати елемент з найменшим значенням у потоці даних. Загальна форма методу має вигляд:

Optional<T> min(Comparator<? super T> comparator)

тут

  • T – тип елементів потоку даних;
  • Optional<T> – результат, що містить найменше значення елементу в потоці даних. Щоб отримати елемент потрібно використати метод get();
  • comparator – функція порівняння двох значень типу T, яка представлена лямбда-виразом;
  • Comparator<? super T> – тип стандартного функціонального інтерфейсу Java, який використовується для порівняння двох значень узагальненого типу T.

Метод min() є кінцевою операцією.

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

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

// Клас, що реалізує відрізок, заданий координатами точок (x1, y1), (x2, y2)
class Line {

  // Внутрішні поля класу
  private double x1, y1;
  private double x2, y2;

  // Конструктор
  public Line(double _x1, double _y1, double _x2, double _y2) {
    x1 = _x1; y1 = _y1; x2 = _x2; y2 = _y2;
  }

  // Методи доступу
  public double getX1() { return x1; }
  public double getY1() { return y1; }
  public double getX2() { return x2; }
  public double getY2() { return y2; }

  // Метод, що повертає довжину відрізка
  public double length() {
    return Math.sqrt((x1-x2)*(x1-x2) + (y1-y2)*(y1-y2));
  }
}

public class StreamAPI {

  public static void main(String[] args) {
    // Метод min() - отримати мінімальне значення елементу в потоці
    // 1. Створити набір відрізків типу Line
    ArrayList<Line> AL = new ArrayList<Line>();
    AL.add(new Line(5.0, 6.0, 8.0, 9.5));
    AL.add(new Line(1.1, 2.3, 8.1, 5.4));
    AL.add(new Line(3.2, 2.8, 1.7, 4.4));
    AL.add(new Line(6.1, 2.5, 3.1, 4.0));
    AL.add(new Line(-1.2, 3.8, -4.3, 2.5));

    // 2. Отримати потік даних типу Line з набору AL
    Stream<Line> stream = AL.stream();

    // 3. Оголосити компаратор - метод порівняння двох значень типу Line
    Comparator<Line> comparator;
    comparator = (line1, line2) -> {
      // порівняти довжини відрізків
      if (line1.length()>line2.length()) return 1;
      if (line1.length()<line2.length()) return -1;
      return 0;
    };

    // 4. Знайти лінію з мінімальним значенням довжини
    Optional<Line> minLine = stream.min(comparator);

    // 5. Вивести результат
    System.out.println("minLine = ( " + 
                       minLine.get().getX1() + "; " +
                       minLine.get().getY1() + "), ( " +
                       minLine.get().getX2() + "; " +
                       minLine.get().getY2() + ")");
    System.out.println("length = " + minLine.get().length());
  }
}

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

minLine = ( 3.2; 2.8), ( 1.7; 4.4)
length = 2.1931712199461315

 

6. Метод reduce(). Отримати заданий результат з елементів потоку. Приклад

Метод reduce() належить до операцій зведення так само як методи min(), max(), count(). В методі reduce() можна звести потік до єдиного значення за власно-розробленим критерієм.

Приклади таких критеріїв:

  • обчислити суму елементів потоку даних;
  • обчислити добуток елементів потоку даних;
  • інші довільні критерії.

Метод reduce() має декілька загальних форм. Нижче наведено дві найбільш вживані:

Optional<T> reduce(BinaryOperator<T> метод_накопичення)
T reduce(T значення_ідентичності, BinaryOperator<T> метод_накопичення)

тут

  • T тип елементів потоку;
  • BinaryOperator<T> – тип стандартного фунціонального інтерфейсу, в якому міститься метод apply(). Стандартний функціональний інтерфейс BinaryOperator<T> оголошується в пакеті java.util.function;
  • метод_накопичення – це є лямбда-вираз, що реалізує функцію накопичення. Функція накопичення оперує двома значеннями та повертає деякий результат;
  • значення_ідентичності – значення, що приводить до отримання того самого значення елементу з потоку даних. Наприклад, якщо значення елементу рівне x, то для операції просумовування значення_ідентичності=0 (0+x=x). Для операції множення значення_ідентичності=1 (1*x=x).

При написанні коду операції накопичення метод_накопичення потрібно дотримуватись наступних обмежень:

  • обмеження без збереження стану означає, що обробка кожного елементу потоку здійснюється окремо;
  • обмеження без втручання означає, що джерело даних не змінюється в тілі методу (операції);
  • операція, що застосовується до елементів потоку повинна бути асоціативна (a+b=b+a).

Метод reduce() є кінцевою операцією (terminated operation).

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

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

public class StreamAPI {

  public static void main(String[] args) {
    // Метод reduce() - застосовує функцію зведення до елементів потоку.
    // У прикладі обчислюється середнє арифметичне елементів потоку даних
    // 1. Створити набір даних типу Double
    ArrayList<Double> AL = new ArrayList<Double>();
    AL.add(25.0);
    AL.add(17.0);
    AL.add(33.0);
    AL.add(18.0);
    AL.add(27.0);
    AL.add(11.0);

    // 2. Отримати потік даних типу Double з набору AL
    Stream<Double> stream = AL.stream();

    // 3. Оголосити бінарний оператор, який повертає тип Double
    BinaryOperator<Double> accumulator;

    // 4. Для двох операндів реалізувати обчислення суми
    accumulator = (value1, value2) -> {
      return value1+value2;
    };

    // 5. Викликати функцію reduce() та вивести результат
    Optional<Double> sum = stream.reduce(accumulator);
    System.out.println("sum = " + sum.get());
  }
}

Результат виконання програми

sum = 131.0

 

7. Метод toArray(). Створити масив з елементів у викликаючому потоці даних. Приклад

Метод toArray() призначений для конвертування потоку даних в масив типу Object[]. Метод має наступну загальну форму:

Object[] toArray()

Метод є кінцевою операцією.

Приклад. У прикладі потік даних типу Float конвертується у масив типу Object[].

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

public class StreamAPI {

  public static void main(String[] args) {
    // Метод toArray() - конвертувати потік даних в масив типу Object[]
    // 1. Створити набір даних типу Float
    ArrayList<Float> AL = new ArrayList<Float>();
    AL.add(25.0f);
    AL.add(11.3f);
    AL.add(3.8f);
    AL.add(7.7f);
    AL.add(2.4f);
    AL.add(5.5f);

    // 2. Отримати потік даних типу Integer з набору AL
    Stream<Float> stream = AL.stream();

    // 3. Отримати масив типу Object[] з потоку stream
    Object[] AF = stream.toArray();

    // 4. Вивести результат
    System.out.print("AF = { ");
    for (int i=0; i<AF.length; i++)
      System.out.print(AF[i] + " ");
    System.out.println(" }");
  }
}

Результат виконання програми

AF = { 25.0 11.3 3.8 7.7 2.4 5.5 }

 


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