- Функциональное программирование
- Введение
- РЕАЛИЗАЦИЯ ПОСТАВЩИКА
- РЕАЛИЗАЦИЯ CONSUMER И BICONSUMER
- ПРИМЕНЕНИЕ ПРЕДИКАТА И БИПРЕДИКАТА
- РЕАЛИЗАЦИЯ ФУНКЦИИ И БИФУНКЦИИ
- РЕАЛИЗАЦИЯ UNARYOPERATOR И BINARYOPERATOR
- УДОБНЫЕ МЕТОДЫ В ФУНКЦИОНАЛЬНЫХ ИНТЕРФЕЙСАХ
- Optional
- СОЗДАНИЕ ОПЦИИ
- РАБОТА С ПУСТОЙ ОПЦИЕЙ
- Использование потоков
- ПОНИМАНИЕ КОНВЕЙЕРНОГО ПОТОКА
- СОЗДАНИЕ ИСТОЧНИКОВ ПОТОКОВ
- Создание конечных потоков
- Создание бесконечных потоков
- ИСПОЛЬЗОВАНИЕ ОБЩИХ ТЕРМИНАЛЬНЫХ ОПЕРАЦИЙ
- count()
- min() и max()
- findAny() и findFirst()
- allMatch(), anyMatch() и noneMatch()
- forEach()
- reduce()
- collect()
- ИСПОЛЬЗОВАНИЕ ОБЩИХ ПРОМЕЖУТОЧНЫХ ОПЕРАЦИЙ
- filter()
- distinct()
- limit() и skip()
- map()
- flatMap()
- sorted()
- peek()
- СОЗДАНИЕ КОНВЕЙЕРА
- Работа с примитивными потоками
- СОЗДАНИЕ ПРИМИТИВНЫХ ПОТОКОВ
- ОТОБРАЖЕНИЕ ПОТОКОВ
- ИСПОЛЬЗОВАНИЕ ОПЦИЙ С ПРИМИТИВНЫМИ ПОТОКАМИ
- ПОДВЕДЕНИЕ ИТОГОВ СТАТИСТИКИ
- ИЗУЧЕНИЕ ФУНКЦИОНАЛЬНЫХ ИНТЕРФЕЙСОВ ДЛЯ ПРИМИТИВОВ
- Функциональные интерфейсы для boolean
- Функциональные интерфейсы для double, int и long
- СБОР РЕЗУЛЬТАТОВ
- Сбор с использованием базовых коллекторов
- Сборка в карты
- Сбор данных с использованием группировки, разбиения и отображения
- ССЫЛКИ
Функциональное программирование
Введение
- фокусируясь на 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