Функциональное программирование — что нужно знать


Содержание
  1. Функциональное программирование
  2. Введение
  3. РЕАЛИЗАЦИЯ ПОСТАВЩИКА
  4. РЕАЛИЗАЦИЯ CONSUMER И BICONSUMER
  5. ПРИМЕНЕНИЕ ПРЕДИКАТА И БИПРЕДИКАТА
  6. РЕАЛИЗАЦИЯ ФУНКЦИИ И БИФУНКЦИИ
  7. РЕАЛИЗАЦИЯ UNARYOPERATOR И BINARYOPERATOR
  8. УДОБНЫЕ МЕТОДЫ В ФУНКЦИОНАЛЬНЫХ ИНТЕРФЕЙСАХ
  9. Optional
  10. СОЗДАНИЕ ОПЦИИ
  11. РАБОТА С ПУСТОЙ ОПЦИЕЙ
  12. Использование потоков
  13. ПОНИМАНИЕ КОНВЕЙЕРНОГО ПОТОКА
  14. СОЗДАНИЕ ИСТОЧНИКОВ ПОТОКОВ
  15. Создание конечных потоков
  16. Создание бесконечных потоков
  17. ИСПОЛЬЗОВАНИЕ ОБЩИХ ТЕРМИНАЛЬНЫХ ОПЕРАЦИЙ
  18. count()
  19. min() и max()
  20. findAny() и findFirst()
  21. allMatch(), anyMatch() и noneMatch()
  22. forEach()
  23. reduce()
  24. collect()
  25. ИСПОЛЬЗОВАНИЕ ОБЩИХ ПРОМЕЖУТОЧНЫХ ОПЕРАЦИЙ
  26. filter()
  27. distinct()
  28. limit() и skip()
  29. map()
  30. flatMap()
  31. sorted()
  32. peek()
  33. СОЗДАНИЕ КОНВЕЙЕРА
  34. Работа с примитивными потоками
  35. СОЗДАНИЕ ПРИМИТИВНЫХ ПОТОКОВ
  36. ОТОБРАЖЕНИЕ ПОТОКОВ
  37. ИСПОЛЬЗОВАНИЕ ОПЦИЙ С ПРИМИТИВНЫМИ ПОТОКАМИ
  38. ПОДВЕДЕНИЕ ИТОГОВ СТАТИСТИКИ
  39. ИЗУЧЕНИЕ ФУНКЦИОНАЛЬНЫХ ИНТЕРФЕЙСОВ ДЛЯ ПРИМИТИВОВ
  40. Функциональные интерфейсы для boolean
  41. Функциональные интерфейсы для double, int и long
  42. СБОР РЕЗУЛЬТАТОВ
  43. Сбор с использованием базовых коллекторов
  44. Сборка в карты
  45. Сбор данных с использованием группировки, разбиения и отображения
  46. ССЫЛКИ

Функциональное программирование

Введение

  • фокусируясь на API Streams.
  • функциональный интерфейс имеет ровно один абстрактный метод.
    • Пакет java.util.function.
Функциональный интерфейс Тип возврата Имя метода # параметров
Поставщик T получить() 0
Потребитель void принять(T) 1
BiConsumer void принять(T,U) 2 (T, U)
Предикат булево тест(T) 1 (T)
BiPredicate булево test(T,U) 2 (T, U)
Функция R применить(T) 1 (T)
Бифункция R применить(T,U) 2 (T, U)
UnaryOperator T применить(T) 1 (T)
БинарныйОператор T применить(T,T) 2 (T, T)

РЕАЛИЗАЦИЯ ПОСТАВЩИКА

  • Поставщик используется, когда вы хотите генерировать или предоставлять значения, не принимая никаких входных данных.
    • часто используется при конструировании новых объектов.
@FunctionalInterface
public interface Supplier<T> {
   T get();
}
Вход в полноэкранный режим Выход из полноэкранного режима
Supplier<LocalDate> s1 = () -> LocalDate.now();

LocalDate d1 = s1.get();

System.out.println(d1);
Войти в полноэкранный режим Выход из полноэкранного режима

РЕАЛИЗАЦИЯ CONSUMER И BICONSUMER

  • Вы используете Consumer, когда хотите сделать что-то с параметром, но ничего не вернуть.
  • BiConsumer делает то же самое, только принимает два параметра.
@FunctionalInterface
public interface Consumer<T> {
   void accept(T t);
}

@FunctionalInterface
public interface BiConsumer<T, U> {
   void accept(T t, U u);
}
Вход в полноэкранный режим Выход из полноэкранного режима
Consumer<String> c1 = x -> System.out.println(x);

c1.accept("Annie");
Войти в полноэкранный режим Выход из полноэкранного режима
var map = new HashMap<String, Integer>();
BiConsumer<String, Integer> b1 = (k, v) -> map.put(k, v);

b1.accept("chicken", 7);
b1.accept("chick", 1);

System.out.println(map);
Ввести полноэкранный режим Выход из полноэкранного режима

ПРИМЕНЕНИЕ ПРЕДИКАТА И БИПРЕДИКАТА

  • Предикат часто используется при фильтрации или сопоставлении.
  • Предикат BiPredicate подобен предикату Predicate, но принимает два параметра вместо одного.
@FunctionalInterface
public interface Predicate<T> {
   boolean test(T t);
}

@FunctionalInterface
public interface BiPredicate<T, U> {
   boolean test(T t, U u);
}
Вход в полноэкранный режим Выход из полноэкранного режима
Predicate<String> p1 = x -> x.isEmpty();

System.out.println(p1.test(""));  // true
Войти в полноэкранный режим Выход из полноэкранного режима
BiPredicate<String, String> b1 = (string, prefix) -> string.startsWith(prefix);

System.out.println(b1.test("chicken", "chick"));  // true
Ввести полноэкранный режим Выход из полноэкранного режима

РЕАЛИЗАЦИЯ ФУНКЦИИ И БИФУНКЦИИ

  • Функция отвечает за преобразование одного параметра в значение потенциально другого типа и его возврат.
  • Бифункция отвечает за преобразование двух параметров в значение и его возврат.
@FunctionalInterface
public interface Function<T, R> {
   R apply(T t);
}

@FunctionalInterface
public interface BiFunction<T, U, R> {
   R apply(T t, U u);
}
Вход в полноэкранный режим Выход из полноэкранного режима
Function<String, Integer> f1 = x -> x.length();

System.out.println(f1.apply("cluck")); // 5
Войти в полноэкранный режим Выход из полноэкранного режима
BiFunction<String, String, String> b1 = (string, toAdd) -> string.concat(toAdd);

System.out.println(b1.apply("baby ", "chick")); // baby chick
Ввести полноэкранный режим Выход из полноэкранного режима

РЕАЛИЗАЦИЯ UNARYOPERATOR И BINARYOPERATOR

  • UnaryOperator и BinaryOperator требуют, чтобы все параметры типа были одного типа.
  • UnaryOperator преобразует свое значение в значение того же типа.
  • BinaryOperator объединяет два значения в одно того же типа.
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {
  T apply(T t);
}

@FunctionalInterface
public interface BinaryOperator<T> extends BiFunction<T, T, T> {
  T apply(T t1, T t2);
}
Вход в полноэкранный режим Выход из полноэкранного режима
UnaryOperator<String> u1 = x -> x.toUpperCase();

System.out.println(u1.apply("chirp"));  // CHIRP
Войти в полноэкранный режим Выход из полноэкранного режима
BinaryOperator<String> b1 = (string, toAdd) -> string.concat(toAdd);

System.out.println(b1.apply("baby ", "chick")); // baby chick
Ввести полноэкранный режим Выход из полноэкранного режима

УДОБНЫЕ МЕТОДЫ В ФУНКЦИОНАЛЬНЫХ ИНТЕРФЕЙСАХ

  • Некоторые из распространенных функциональных интерфейсов предоставляют ряд полезных методов по умолчанию.
Экземпляр интерфейса Тип возврата метода Имя метода Параметры метода
Потребитель Потребитель andThen() Потребитель
Функция Функция andThen() Функция
Функция Функция компоновать() Функция
Предикат Предикат и() Предикат
Предикат Предикат отрицать()
Предикат Предикат или() Предикат
Predicate<String> egg = s -> s.contains("egg");
Predicate<String> brown = s -> s.contains("brown");

Predicate<String> brownEggs = egg.and(brown);
Predicate<String> otherEggs = egg.and(brown.negate());
Вход в полноэкранный режим Выход из полноэкранного режима
Consumer<String> c1 = x -> System.out.print("1: " + x);
Consumer<String> c2 = x -> System.out.print(",2: " + x);

Consumer<String> combined = c1.andThen(c2);
combined.accept("Annie");              // 1: Annie,2: Annie
Войти в полноэкранный режим Выход из полноэкранного режима
Function<Integer, Integer> before = x -> x + 1;
Function<Integer, Integer> after = x -> x * 2;

Function<Integer, Integer> combined = after.compose(before);
System.out.println(combined.apply(3));   // 8
Ввести полноэкранный режим Выход из полноэкранного режима

Optional

  • Optional создается с помощью фабрики.
    • Представьте опцию как коробку, в которой может быть что-то, а может быть и пусто.
Optional.empty()
Optional.of(<value>)
Войти в полноэкранный режим Выйти из полноэкранного режима

СОЗДАНИЕ ОПЦИИ

10: public static Optional<Double> average(int… scores) {
11:    if (scores.length == 0) return Optional.empty();
12:    int sum = 0;
13:    for (int score: scores) sum += score;
14:    return Optional.of((double) sum / scores.length);
15: }

...

      System.out.println(average(90, 100)); // Optional[95.0]
      System.out.println(average());        // Optional.empty

...

20: Optional<Double> opt = average(90, 100);
21: if (opt.isPresent())
22:    System.out.println(opt.get()); // 95.0
Вход в полноэкранный режим Выход из полноэкранного режима
  • Мы получим исключение, поскольку внутри Optional нет значения.
    • java.util.NoSuchElementException: Значение отсутствует
26: Optional<Double> opt = average();
27: System.out.println(opt.get()); // NoSuchElementException
Вход в полноэкранный режим Выйти из полноэкранного режима
  • часто возникает необходимость использовать empty(), когда значение равно null.
    • Это можно сделать с помощью оператора if или троичного оператора ( ? 🙂 .
Optional o = (value == null) ? Optional.empty() : Optional.of(value);
Вход в полноэкранный режим Выйти из полноэкранного режима

Или

Optional o = Optional.ofNullable(value);
Войти в полноэкранный режим Выйти из полноэкранного режима
Метод Когда опция пуста Когда опция содержит значение
get() Выбрасывает исключение Возвращает значение
ifPresent(Consumer c) Ничего не делает Вызывает потребителя со значением
isPresent() Возвращает false Возвращает true
orElse(T other) Возвращает другой параметр Возвращает значение
orElseGet(Supplier s) Возвращает результат вызова поставщика Возвращает значение
orElseThrow() Выбрасывает исключение NoSuchElementException Возвращает значение
orElseThrow(Supplier s) Выбрасывает исключение, созданное вызывающим поставщиком Возвращает значение
  • ifPresent() -> Вы можете думать об этом как об операторе if без else.
Optional<Double> opt = average(90, 100);
opt.ifPresent(System.out::println);
Вход в полноэкранный режим Выход из полноэкранного режима

РАБОТА С ПУСТОЙ ОПЦИЕЙ

30: Optional<Double> opt = average();
31: System.out.println(opt.orElse(Double.NaN));
32: System.out.println(opt.orElseGet(() -> Math.random()));
Вход в полноэкранный режим Выход из полноэкранного режима
NaN
0.49775932295380165
Войти в полноэкранный режим Выход из полноэкранного режима
30: Optional<Double> opt = average();
31: System.out.println(opt.orElseThrow());
Войти в полноэкранный режим Выход из полноэкранного режима
  • Без указания поставщика для исключения Java выбросит NoSuchElementException.
Exception in thread "main" java.util.NoSuchElementException: 
   No value present
   at java.base/java.util.Optional.orElseThrow(Optional.java:382)
Войти в полноэкранный режим Выход из полноэкранного режима
30: Optional<Double> opt = average();
31: System.out.println(opt.orElseThrow(
32:    () -> new IllegalStateException()));
Войти в полноэкранный режим Выход из полноэкранного режима
Exception in thread "main" java.lang.IllegalStateException
   at optionals.Methods.lambda$orElse$1(Methods.java:30)
   at java.base/java.util.Optional.orElseThrow(Optional.java:408)
Войти в полноэкранный режим Выход из полноэкранного режима

Использование потоков

  • Поток в Java — это последовательность данных.
  • Конвейер потока состоит из операций, которые выполняются над потоком для получения результата.

ПОНИМАНИЕ КОНВЕЙЕРНОГО ПОТОКА

  • трубопровод потока как сборочный конвейер на заводе.

    • операции над потоком
  • В потоковом конвейере есть три части

    • источник: откуда поступает поток
    • промежуточные операции: Преобразование потока в другой поток.
    • конечная операция: Фактически производит результат.
  • После завершения терминальной операции поток больше не действителен.

СОЗДАНИЕ ИСТОЧНИКОВ ПОТОКОВ

  • В Java потоки представлены интерфейсом Stream, определенным в пакете java.util.stream.

Создание конечных потоков

11: Stream<String> empty = Stream.empty();          // count = 0
12: Stream<Integer> singleElement = Stream.of(1);   // count = 1
13: Stream<Integer> fromArray = Stream.of(1, 2, 3); // count = 3
14: var list = List.of("a", "b", "c");
15: Stream<String> fromList = list.stream();
Вход в полноэкранный режим Выход из полноэкранного режима

Создание бесконечных потоков

7: Stream<Double> randoms = Stream.generate(Math::random);
18: Stream<Integer> oddNumbers = Stream.iterate(1, n -> n + 2);
Войти в полноэкранный режим Выход из полноэкранного режима
  • перегруженный пример iterate()
19: Stream<Integer> oddNumberUnder100 = Stream.iterate(
20:    1,                // seed
21:    n -> n < 100,     // Predicate to specify when done
22:    n -> n + 2);      // UnaryOperator to get next value
Войти в полноэкранный режим Выход из полноэкранного режима
Метод Конечный или бесконечный? Примечания
Stream.empty() Конечный Создает поток с нулевыми элементами
Stream.of(varargs) Finite Создает поток с элементами, перечисленными в списке
coll.stream() Finite Создает поток из коллекции
coll.parallelStream() Finite Создает поток из коллекции, где поток может работать параллельно
Stream.generate(supplier) Бесконечный Создает поток, вызывая поставщика для каждого элемента по запросу
Stream.iterate(seed, unaryOperator) Бесконечный Создает поток, используя seed для первого элемента, а затем вызывая UnaryOperator для каждого последующего элемента по запросу
Stream.iterate(seed, predicate, unaryOperator) Конечный или бесконечный Создает поток, используя seed для первого элемента, а затем вызывая UnaryOperator для каждого последующего элемента по запросу. Останавливается, если предикат возвращает false

ИСПОЛЬЗОВАНИЕ ОБЩИХ ТЕРМИНАЛЬНЫХ ОПЕРАЦИЙ

  • Вы можете выполнить терминальную операцию без каких-либо промежуточных операций, но не наоборот.
  • Вычитания — это особый тип терминальной операции, когда все содержимое потока объединяется в один примитив или объект.
Метод Что происходит для бесконечных потоков Возвращаемое значение Сокращение
count() Не завершается long Да
min()
max()
Не завершается Необязательно Да
findAny()
findFirst()
Завершает Необязательно Нет
allMatch()
anyMatch()
noneMatch()
Иногда завершается булеан Нет
forEach() Не завершается void Нет
reduce() Не завершается Варьируется Да
собирать() Не завершается Разное Да

count()

  • Метод count() определяет количество элементов в конечном потоке.
long count()
Вход в полноэкранный режим Выход из полноэкранного режима
Stream<String> s = Stream.of("monkey", "gorilla", "bonobo");
System.out.println(s.count());   // 3
Войти в полноэкранный режим Выход из полноэкранного режима

min() и max()

  • Методы min() и max() позволяют передать пользовательский компаратор и найти наименьшее или наибольшее значение в конечном потоке в соответствии с этим порядком сортировки.
Optional<T> min(Comparator<? super T> comparator)
Optional<T> max(Comparator<? super T> comparator)
Вход в полноэкранный режим Выход из полноэкранного режима
Stream<String> s = Stream.of("monkey", "ape", "bonobo");
Optional<String> min = s.min((s1, s2) -> s1.length()-s2.length());
min.ifPresent(System.out::println); // ape
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Возвращает необязательное значение, так как бывают случаи, когда невозможно найти min или max.
Optional<?> minEmpty = Stream.empty().min((s1, s2) -> 0);
System.out.println(minEmpty.isPresent()); // false
Ввести полноэкранный режим Выход из полноэкранного режима

findAny() и findFirst()

  • Методы findAny() и findFirst() возвращают элемент потока, если только поток не пуст.
    • Если поток пуст, они возвращают пустой Optional.
  • может завершиться бесконечным потоком.
Optional<T> findAny()
Optional<T> findFirst()
Вход в полноэкранный режим Выход из полноэкранного режима
Stream<String> s = Stream.of("monkey", "gorilla", "bonobo");
Stream<String> infinite = Stream.generate(() -> "chimp");

s.findAny().ifPresent(System.out::println);        // monkey (usually)
infinite.findAny().ifPresent(System.out::println); // chimp
Войти в полноэкранный режим Выход из полноэкранного режима

allMatch(), anyMatch() и noneMatch()

  • Методы allMatch(), anyMatch() и noneMatch() ищут поток и возвращают информацию о том, как этот поток относится к предикату.
  • Они могут завершаться или не завершаться для бесконечных потоков.
  • Они не являются сокращениями, поскольку не обязательно просматривают все элементы.
boolean anyMatch(Predicate <? super T> predicate)
boolean allMatch(Predicate <? super T> predicate)
boolean noneMatch(Predicate <? super T> predicate)
Вход в полноэкранный режим Выход из полноэкранного режима
var list = List.of("monkey", "2", "chimp");
Stream<String> infinite = Stream.generate(() -> "chimp");
Predicate<String> pred = x -> Character.isLetter(x.charAt(0));

System.out.println(list.stream().anyMatch(pred));  // true
System.out.println(list.stream().allMatch(pred));  // false
System.out.println(list.stream().noneMatch(pred)); // false
System.out.println(infinite.anyMatch(pred));       // true
Войти в полноэкранный режим Выход из полноэкранного режима

forEach()

  • обычно выполняется итерация по элементам потока.
  • на бесконечном потоке не завершается.
  • это не является сокращением.
void forEach(Consumer<? super T> action)
Вход в полноэкранный режим Выход из полноэкранного режима
tream<String> s = Stream.of("Monkey", "Gorilla", "Bonobo");
s.forEach(System.out::print); // MonkeyGorillaBonobo
Войти в полноэкранный режим Выход из полноэкранного режима

reduce()

  • Метод reduce() объединяет поток в один объект.
T reduce(T identity, BinaryOperator<T> accumulator)

Optional<T> reduce(BinaryOperator<T> accumulator)

<U> U reduce(U identity, BiFunction<U,? super T,U> accumulator, BinaryOperator<U> combiner)
Войти в полноэкранный режим Выход из полноэкранного режима
  • Тождество — это начальное значение редукции
  • Накопитель объединяет текущий результат с текущим значением в потоке.
var array = new String[] { "w", "o", "l", "f" };
var result = "";
for (var s: array) result = result + s;
System.out.println(result); // wolf
Войти в полноэкранный режим Выход из полноэкранного режима
Stream<String> stream = Stream.of("w", "o", "l", "f");
String word = stream.reduce("", (s, c) -> s + c);
System.out.println(word); // wolf
Войти в полноэкранный режим Выход из полноэкранного режима

ИЛИ

Stream<String> stream = Stream.of("w", "o", "l", "f");
String word = stream.reduce("", String::concat);
System.out.println(word); // wolf
Войти в полноэкранный режим Выйти из полноэкранного режима
Stream<Integer> stream = Stream.of(3, 5, 6);
System.out.println(stream.reduce(1, (a, b) -> a*b));  // 90
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Существует три варианта выбора того, что находится в Дополнительном потоке.
    • Если поток пуст, возвращается пустой Optional.
    • Если в потоке есть один элемент, то возвращается он.
    • Если в потоке несколько элементов, то для их объединения применяется аккумулятор.
BinaryOperator<Integer> op = (a, b) -> a * b;
Stream<Integer> empty = Stream.empty();
Stream<Integer> oneElement = Stream.of(3);
Stream<Integer> threeElements = Stream.of(3, 5, 6);

empty.reduce(op).ifPresent(System.out::println);         // no output
oneElement.reduce(op).ifPresent(System.out::println);    // 3
threeElements.reduce(op).ifPresent(System.out::println); // 90
Вход в полноэкранный режим Выйти из полноэкранного режима
  • Третья сигнатура метода используется, когда мы имеем дело с различными типами.
Stream<String> stream = Stream.of("w", "o", "l", "f!");
int length = stream.reduce(0, (i, s) -> i+s.length(), (a, b) -> a+b);
System.out.println(length); // 5
Войти в полноэкранный режим Выход из полноэкранного режима
  • i — целое число, s — строка.
  • Комбинатор, используемый в параллельных потоках.

collect()

  • это особый тип редукции, называемый мутабельной редукцией.
<R> R collect(Supplier<R> supplier, 
   BiConsumer<R, ? super T> accumulator, 
   BiConsumer<R, R> combiner)

<R,A> R collect(Collector<? super T, A,R> collector)
Вход в полноэкранный режим Выход из полноэкранного режима
Stream<String> stream = Stream.of("w", "o", "l", "f");

StringBuilder word = stream.collect(
   StringBuilder::new,
   StringBuilder::append, 
   StringBuilder::append)

System.out.println(word); // wolf
Войти в полноэкранный режим Выход из полноэкранного режима
  • Комбинатор, используемый в параллельных потоках;
Stream<String> stream = Stream.of("w", "o", "l", "f");

TreeSet<String> set = stream.collect(
   TreeSet::new, 
   TreeSet::add,
   TreeSet::addAll);

System.out.println(set); // [f, l, o, w]
Войти в полноэкранный режим Выход из полноэкранного режима
Stream<String> stream = Stream.of("w", "o", "l", "f");
TreeSet<String> set = 
   stream.collect(Collectors.toCollection(TreeSet::new));
System.out.println(set); // [f, l, o, w]
Войти в полноэкранный режим Выход из полноэкранного режима
Stream<String> stream = Stream.of("w", "o", "l", "f");
Set<String> set = stream.collect(Collectors.toSet());
System.out.println(set); // [f, w, l, o]
Войти в полноэкранный режим Выход из полноэкранного режима

ИСПОЛЬЗОВАНИЕ ОБЩИХ ПРОМЕЖУТОЧНЫХ ОПЕРАЦИЙ

  • Промежуточная операция создает поток в качестве своего результата.

filter()

  • Метод filter() возвращает поток с элементами, которые соответствуют заданному выражению.
Stream<T> filter(Predicate<? super T> predicate)
Вход в полноэкранный режим Выход из полноэкранного режима
Stream<String> s = Stream.of("monkey", "gorilla", "bonobo");
s.filter(x -> x.startsWith("m"))
   .forEach(System.out::print); // monkey
Войти в полноэкранный режим Выйти из полноэкранного режима

distinct()

  • Метод distinct() возвращает поток с удаленными дублирующимися значениями.
Stream<T> distinct()
Войти в полноэкранный режим Выход из полноэкранного режима
Stream<String> s = Stream.of("duck", "duck", "duck", "goose");
s.distinct()
   .forEach(System.out::print); // duckgoose
Войти в полноэкранный режим Выход из полноэкранного режима

limit() и skip()

  • Методы limit() и skip() могут сделать поток меньше, или сделать конечный поток из бесконечного.
Stream<T> limit(long maxSize)
Stream<T> skip(long n)
Войти в полноэкранный режим Выход из полноэкранного режима
Stream<Integer> s = Stream.iterate(1, n -> n + 1);
s.skip(5)
   .limit(2)
   .forEach(System.out::print); // 67
Войти в полноэкранный режим Выйти из полноэкранного режима

map()

  • Метод map() создает отображение один-к-одному от элементов в потоке к элементам следующего шага в потоке.
<R> Stream<R> map(Function<? super T, ? extends R> mapper)
Войти в полноэкранный режим Выход из полноэкранного режима
Stream<String> s = Stream.of("monkey", "gorilla", "bonobo");
s.map(String::length)
   .forEach(System.out::print); // 676
Вход в полноэкранный режим Выход из полноэкранного режима

flatMap()

  • Метод flatMap() берет каждый элемент в потоке и делает все элементы, которые он содержит, элементами верхнего уровня в одном потоке.
<R> Stream<R> flatMap(
   Function<? super T, ? extends Stream<? extends R>> mapper)
Войти в полноэкранный режим Выход из полноэкранного режима
List<String> zero = List.of();
var one = List.of("Bonobo");
var two = List.of("Mama Gorilla", "Baby Gorilla");
Stream<List<String>> animals = Stream.of(zero, one, two);

animals.flatMap(m -> m.stream())
   .forEach(System.out::println);
Войти в полноэкранный режим Выход из полноэкранного режима
Bonobo
Mama Gorilla
Baby Gorilla
Войти в полноэкранный режим Выход из полноэкранного режима

sorted()

  • Метод sorted() возвращает поток с отсортированными элементами.
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
Войти в полноэкранный режим Выход из полноэкранного режима
Stream<String> s = Stream.of("brown-", "bear-");
s.sorted()
   .forEach(System.out::print); // bear-brown
Войти в полноэкранный режим Выход из полноэкранного режима
Stream<String> s = Stream.of("brown bear-", "grizzly-");
s.sorted(Comparator.reverseOrder())
   .forEach(System.out::print); // grizzly-brown bear-
Войти в полноэкранный режим Выход из полноэкранного режима

peek()

  • позволяет нам выполнять операции с потоком без фактического изменения потока.
Stream<T> peek(Consumer<? super T> action)
Войти в полноэкранный режим Выход из полноэкранного режима
var stream = Stream.of("black bear", "brown bear", "grizzly");
long count = stream.filter(s -> s.startsWith("g"))
   .peek(System.out::println).count();              // grizzly
System.out.println(count);                          // 1
Войти в полноэкранный режим Выход из полноэкранного режима

СОЗДАНИЕ КОНВЕЙЕРА

var list = List.of("Toby", "Anna", "Leroy", "Alex");
list.stream()
   .filter(n -> n.length() == 4)
   .sorted()
   .limit(2)
   .forEach(System.out::println); 
Вход в полноэкранный режим Выход из полноэкранного режима
Stream.generate(() -> "Elsa")
   .filter(n -> n.length() == 4)
   .limit(2)
   .sorted()
   .forEach(System.out::println);
Войти в полноэкранный режим Выход из полноэкранного режима

Работа с примитивными потоками

СОЗДАНИЕ ПРИМИТИВНЫХ ПОТОКОВ

  • IntStream: Используется для примитивных типов int, short, byte и char.
  • LongStream: Используется для примитивного типа long
  • DoubleStream: Используется для примитивных типов double и float
Метод Примитивный поток Описание
OptionalDouble average() IntStream
LongStream
DoubleStream
Среднее арифметическое значение элементов
Stream<T> boxed() IntStream
LongStream
DoubleStream
Поток, где T — класс-обертка, связанный с примитивным значением
OptionalInt max()
OptionalLong max()
OptionalDouble max()
IntStream
LongStream
DoubleStream
Максимальный элемент потока
OptionalInt min()
OptionalLong min()
OptionalDouble min()
IntStream
LongStream
DoubleStream
Минимальный элемент потока
IntStream range(int a, int b)
LongStream range(long a, long b)
IntStream
LongStream
Возвращает примитивный поток от a (включительно) до b (исключительно)
IntStream rangeClosed(int a, int b)
LongStream rangeClosed(long a, long b)
IntStream
LongStream
Возвращает примитивный поток от a (включительно) до b (включительно)
int sum()
long sum()
double sum()
IntStream
LongStream
DoubleStream
Возвращает сумму элементов в потоке
IntSummaryStatistics summaryStatistics()
LongSummaryStatistics summaryStatistics()
DoubleSummaryStatistics summaryStatistics()
IntStream
LongStream
DoubleStream
Возвращает объект, содержащий многочисленные статистические данные потока, такие как среднее значение, min, max и т.д.

Таблица — Общие примитивные методы потоков

  • Примеры:

  • Создание потоков

DoubleStream empty = DoubleStream.empty();

DoubleStream oneValue = DoubleStream.of(3.14);
oneValue.forEach(System.out::println);

DoubleStream varargs = DoubleStream.of(1.0, 1.1, 1.2);
varargs.forEach(System.out::println);
Вход в полноэкранный режим Выход из полноэкранного режима
3.14
1.0
1.1
1.2
Войти в полноэкранный режим Выход из полноэкранного режима
  • Создание бесконечных потоков
var random = DoubleStream.generate(Math::random);
var fractions = DoubleStream.iterate(.5, d -> d / 2);
random.limit(3).forEach(System.out::println);
fractions.limit(3).forEach(System.out::println);
Войти в полноэкранный режим Выход из полноэкранного режима
0.07890654781186413
0.28564363465842346
0.6311403511266134
0.5
0.25
0.125
Войти в полноэкранный режим Выход из полноэкранного режима
  • Использование диапазона
IntStream range = IntStream.range(1, 6);
range.forEach(System.out::println);
Войти в полноэкранный режим Выход из полноэкранного режима
IntStream rangeClosed = IntStream.rangeClosed(1, 5);
rangeClosed.forEach(System.out::println);
Войти в полноэкранный режим Выход из полноэкранного режима

ОТОБРАЖЕНИЕ ПОТОКОВ

  • Другим способом создания примитивного потока является маппинг из другого типа потока.
Исходный класс потока Создать Stream Создать DoubleStream Создать IntStream Для создания LongStream
Stream map() mapToDouble() mapToInt() mapToLong()
DoubleStream mapToObj() map() mapToInt() mapToLong()
IntStream mapToObj() mapToDouble() map() mapToLong()
LongStream mapToObj() mapToDouble() mapToInt() map()

Методы отображения между типами потоков

  • пример
Stream<String> objStream = Stream.of("penguin", "fish");
IntStream intStream = objStream.mapToInt(s -> s.length());
Вход в полноэкранный режим Выход из полноэкранного режима
Класс исходного потока Создать Stream Создать DoubleStream Создать IntStream Для создания LongStream
Stream Функция ToDoubleFunction ToIntFunction ToLongFunction
DoubleStream Двойная функция Двойной унарный оператор Функция DoubleToInt Функция DoubleToLong
IntStream IntFunction Функция IntToDouble ИнтУнарный оператор Функция IntToLong
LongStream Long Function LongToDouble Функция LongToInt Функция LongUnary Operator

Параметры функции при отображении между типами потоков

  • flatmap() на примитивных потоках имеет другое имя метода
var integerList = new ArrayList<Integer>();
IntStream ints = integerList.stream()
.flatMapToInt(x -> IntStream.of(x));
DoubleStream doubles = integerList.stream()
.flatMapToDouble(x -> DoubleStream.of(x));
LongStream longs = integerList.stream()
.flatMapToLong(x -> LongStream.of(x));
Вход в полноэкранный режим Выход из полноэкранного режима

ИСПОЛЬЗОВАНИЕ ОПЦИЙ С ПРИМИТИВНЫМИ ПОТОКАМИ

var stream = IntStream.rangeClosed(1,10);
OptionalDouble optional = stream.average();

optional.ifPresent(System.out::println);                  // 5.5
System.out.println(optional.getAsDouble());               // 5.5
System.out.println(optional.orElseGet(() -> Double.NaN)); // 5.5
Войти в полноэкранный режим Выход из полноэкранного режима
  • OptionalDouble — для примитива, а Optional — для класса-обертки Double.
  • getAsDouble(), а не get().
  • orElseGet() принимает DoubleSupplier вместо Supplier.
OptionalDouble OptionalInt OptionalLong
Получение как примитив getAsDouble() getAsInt() getAsLong()
тип параметра orElseGet() DoubleSupplier IntSupplier LongSupplier
Возвращаемый тип параметров max() и min() OptionalDouble OptionalInt OptionalLong
Возвращаемый тип sum() double int long
Возвращаемый тип среднего() OptionalDouble OptionalDouble OptionalDouble

Параметры функции при сопоставлении между типами потоков

  • Метод sum() не возвращает Optional.
  • Метод min() возвращает OptionalDouble.
5: LongStream longs = LongStream.of(5, 10);
6: long sum = longs.sum();
7: System.out.println(sum);     // 15
8: DoubleStream doubles = DoubleStream.generate(() -> Math.PI);
9: OptionalDouble min = doubles.min(); // runs infinitely
Вход в полноэкранный режим Выход из полноэкранного режима

ПОДВЕДЕНИЕ ИТОГОВ СТАТИСТИКИ

private static int range(IntStream ints) {
   IntSummaryStatistics stats = ints.summaryStatistics();
   if (stats.getCount() == 0) throw new RuntimeException();
   return stats.getMax()-stats.getMin();
}
Вход в полноэкранный режим Выход из полноэкранного режима
  • Сводная статистика включает следующее:
    • Наименьшее число (минимум): getMin()
    • Наибольшее число (максимум): getMax()
    • Среднее значение: getAverage()
    • Сумма: getSum()
    • Количество значений: getCount()

ИЗУЧЕНИЕ ФУНКЦИОНАЛЬНЫХ ИНТЕРФЕЙСОВ ДЛЯ ПРИМИТИВОВ

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

  • BooleanSupplier
boolean getAsBoolean()
Вход в полноэкранный режим Выход из полноэкранного режима
12: BooleanSupplier b1 = () -> true;
13: BooleanSupplier b2 = () -> Math.random()> .5;
14: System.out.println(b1.getAsBoolean());  // true
15: System.out.println(b2.getAsBoolean());  // false
Войти в полноэкранный режим Выход из полноэкранного режима

Функциональные интерфейсы для double, int и long

Общие функциональные интерфейсы для примитивов

Функциональные интерфейсы # параметры Тип возврата Одиночный абстрактный метод
DoubleSupplier
IntSupplier
LongSupplier
0 двойной
int
long
getAsDouble
getAsInt
getAsLong
DoubleConsumer
IntConsumer
LongConsumer
1 (double)
1 (int)
1 (long)
void принимать
DoublePredicate
IntPredicate
LongPredicate
1 (double)
1 (int)
1 (long)
булево тест
DoubleFunction<R>
IntFunction<R> LongFunction<R>
1 (double)
1 (int)
1 (long)
R применить
DoubleUnaryOperator
IntUnaryOperator
LongUnaryOperator
1 (double)
1(int)
1(long)
двойной
int
long
applyAsDouble
applyAsInt
applyAsLong
DoubleBinaryOperator
IntBinaryOperator
LongBinaryOperator
2 (double, double)
2 (int, int)
2 (long, long)
double
int
long
applyAsDouble
applyAsInt
applyAsLong

Функциональные интерфейсы, специфичные для примитивов

Функциональные интерфейсы # параметры Тип возврата Одиночный абстрактный метод
ToDoubleFunction
ToIntFunction
ToLongFunction
1 (T) двойной
int
long
applyAsDouble
applyAsInt
applyAsLong
ToDoubleBiFunction
ToIntBiFunction
ToLongBiFunction
2 (T, U) double
int
long
applyAsDouble
applyAsInt
applyAsLong
DoubleToIntFunction
DoubleToLongFunction
IntToDoubleFunction
IntToLongFunction
LongToDoubleFunction
LongToIntFunction
1 (двойной)
1 (double)
1 (int)
1 (int)
1 (long)
1 (длинный)
int
длинный
двойной
длинный
двойной
int
applyAsInt
applyAsLong
applyAsDouble
applyAsLong
applyAsDouble
applyAsInt
ObjDoubleConsumer
ObjIntConsumer
ObjLongConsumer
2 (T, double)
2 (T, int)
2 (T, long)
void принимать

СБОР РЕЗУЛЬТАТОВ

  • Существует множество предопределенных коллекторов.
    • Эти коллекторы доступны через статические методы интерфейса Collectors.

Примеры коллекторов группировки/разделения

Коллектор Описание Возвращаемое значение при передаче коллектору
averagingDouble(ToDoubleFunction f)
averagingInt(ToIntFunction f)
averagingLong(ToLongFunction f)
Вычисляет среднее значение для наших трех основных примитивных типов Double
counting() Подсчитывает количество элементов Long
groupingBy(Function f)
groupingBy(Function f, Collector dc)
groupingBy(Function f, Supplier s, Collector dc)
Создает карту, группирующуюся по указанной функции, с необязательным типом поставщика карты и необязательным нисходящим коллектором Map>
joining(CharSequence cs) Создает одну строку, используя cs в качестве разделителя между элементами, если он указан Строка
maxBy(Comparator c)
minBy(Comparator c)
Находит самый большой/самый маленький элемент Необязательный
mapping(Function f, Collector dc) Добавляет еще один уровень коллекторов Коллектор
partitioningBy(Predicate p)
partitioningBy(Predicate p, Collector dc)
Создает карту, группирующуюся по указанному предикату с необязательным дополнительным коллектором нижнего уровня Map>
суммированиеDouble(ToDoubleFunction f)
суммирующийInt(ToIntFunction f)
summarizingLong(ToLongFunction f)
Вычисляет среднее значение, min, max и т.д. DoubleSummaryStatistics IntSummaryStatistics LongSummaryStatistics
суммированиеDouble(ToDoubleFunction f)
суммированиеInt(ToIntFunction f)
summingLong(ToLongFunction f)
Вычисляет сумму для наших трех основных примитивных типов Double
Integer
Long
toList()
toSet()
Создает произвольный тип списка или набора Список
Набор
toCollection(Supplier s) Создает коллекцию указанного типа Коллекция
toMap(Function k, Function v)
toMap(Function k, Function v, BinaryOperator m)
toMap(Function k, Function v, BinaryOperator m, Supplier s)
Создает карту, используя функции для отображения ключей, значений, необязательную функцию слияния и необязательного поставщика типа карты. Карта

Сбор с использованием базовых коллекторов

var ohMy = Stream.of("lions", "tigers", "bears");
String result = ohMy.collect(Collectors.joining(", "));
System.out.println(result); // lions, tigers, bears
Вход в полноэкранный режим Выход из полноэкранного режима
var ohMy = Stream.of("lions", "tigers", "bears");
Double result = ohMy.collect(Collectors.averagingInt(String::length));
System.out.println(result); // 5.333333333333333
Войти в полноэкранный режим Выйти из полноэкранного режима
var ohMy = Stream.of("lions", "tigers", "bears");
TreeSet<String> result = ohMy
   .filter(s -> s.startsWith("t"))
   .collect(Collectors.toCollection(TreeSet::new));
System.out.println(result); // [tigers]
Войти в полноэкранный режим Выход из полноэкранного режима

Сборка в карты

  • необходимо указать две функции.
    • Первая функция указывает сборщику, как создать ключ.
    • Вторая функция указывает сборщику, как создать значение.
var ohMy = Stream.of("lions", "tigers", "bears");
Map<String, Integer> map = ohMy.collect(
   Collectors.toMap(s -> s, String::length));
System.out.println(map); // {lions=5, bears=5, tigers=6}
Вход в полноэкранный режим Выход из полноэкранного режима
  • В случае хранения одного и того же ключа два раза, коллектор не имеет понятия, что делать, он «решает» проблему, выбрасывая исключение и делая ее нашей проблемой.
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Integer, String> map = ohMy.collect(Collectors.toMap(
   String::length, 
   k -> k)); // BAD
Вход в полноэкранный режим Выход из полноэкранного режима
Exception in thread "main" 
   java.lang.IllegalStateException: Duplicate key 5
Войти в полноэкранный режим Выход из полноэкранного режима
  • мы можем
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Integer, String> map = ohMy.collect(Collectors.toMap(
   String::length,
   k -> k, 
  (s1, s2) -> s1 + "," + s2));
System.out.println(map);            // {5=lions,bears, 6=tigers}
System.out.println(map.getClass()); // class java.util.HashMap
Войти в полноэкранный режим Выйти из полноэкранного режима
  • если вы хотите сохранить в TreeMap
var ohMy = Stream.of("lions", "tigers", "bears");
TreeMap<Integer, String> map = ohMy.collect(Collectors.toMap(
   String::length, 
   k -> k, 
   (s1, s2) -> s1 + "," + s2,
   TreeMap::new));
System.out.println(map); //         // {5=lions,bears, 6=tigers}
System.out.println(map.getClass()); // class java.util.TreeMap
Войти в полноэкранный режим Выйти из полноэкранного режима

Сбор данных с использованием группировки, разбиения и отображения

  • Предположим, что мы хотим получить группы имен по их длине.
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Integer, List<String>> map = ohMy.collect(
   Collectors.groupingBy(String::length));
System.out.println(map);    // {5=[lions, bears], 6=[tigers]}
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Обратите внимание, что функция, которую вы вызываете в groupingBy(), не может вернуть null. Она не допускает нулевых ключей.

  • Предположим, что мы не хотим использовать List в качестве значения в карте и предпочитаем вместо него Set.

var ohMy = Stream.of("lions", "tigers", "bears");
Map<Integer, Set<String>> map = ohMy.collect(
   Collectors.groupingBy(
      String::length, 
      Collectors.toSet()));
System.out.println(map);    // {5=[lions, bears], 6=[tigers]}
Вход в полноэкранный режим Выход из полноэкранного режима
  • Мы можем даже изменить тип возвращаемой Map с помощью еще одного параметра.
var ohMy = Stream.of("lions", "tigers", "bears");
TreeMap<Integer, Set<String>> map = ohMy.collect(
   Collectors.groupingBy(
      String::length, 
      TreeMap::new, 
      Collectors.toSet()));
System.out.println(map); // {5=[lions, bears], 6=[tigers]}
Вход в полноэкранный режим Выйти из полноэкранного режима
  • другой параметр
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Integer, Long> map = ohMy.collect(
   Collectors.groupingBy(
      String::length, 
      Collectors.counting()));
System.out.println(map);    // {5=2, 6=1}
Войти в полноэкранный режим Выйти из полноэкранного режима
  • Предположим, что мы хотим получить первую букву первого животного в алфавитном порядке каждой длины.
    • mapping() коллектор, который позволяет нам спуститься на уровень ниже и добавить еще один коллектор.
    • принимает два параметра: функцию для значения и способ его дальнейшей группировки.
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Integer, Optional<Character>> map = ohMy.collect(
   Collectors.groupingBy(
      String::length,
      Collectors.mapping(
         s -> s.charAt(0), 
         Collectors.minBy((a, b) -> a -b))));
System.out.println(map);    // {5=Optional[b], 6=Optional[t]}
Вход в полноэкранный режим Выход из полноэкранного режима
  • Разбиение на разделы — это особый случай группировки.
    • две возможные группы — true и false.
    • В отличие от groupingBy(), мы не можем изменить тип возвращаемой карты.
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Boolean, List<String>> map = ohMy.collect(
   Collectors.partitioningBy(s -> s.length() <= 5));
System.out.println(map);    // {false=[tigers], true=[lions, bears]}
Вход в полноэкранный режим Выход из полноэкранного режима
  • модификация из списка в множество
var ohMy = Stream.of("lions", "tigers", "bears");
Map<Boolean, Set<String>> map = ohMy.collect(
   Collectors.partitioningBy(
      s -> s.length() <= 7, 
      Collectors.toSet()));
System.out.println(map);    // {false=[], true=[lions, tigers, bears]}
Вход в полноэкранный режим Выход из полноэкранного режима

ССЫЛКИ

Блог Потоки Java

Оцените статью
Procodings.ru
Добавить комментарий