Stream Flashcards
Что такое Stream?
Интерфейс java.util.Stream представляет собой последовательность элементов, над которой можно производить различные операции.
Операции над стримами бывают или промежуточными (intermediate) или конечными (terminal). Конечные операции возвращают результат определенного типа, а промежуточные операции возвращают тот же стрим. Таким образом вы можете строить цепочки из несколько операций над одним и тем же стримом.
У стрима может быть сколько угодно вызовов промежуточных операций и последним вызов конечной операции. При этом все промежуточные операции выполняются лениво и пока не будет вызвана конечная операция никаких действий на самом деле не происходит (похоже на создание объекта Thread или Runnable, без вызова start()).
Стримы создаются на основе каких-либо источников, например Collection.
Ассоциативные массивы (maps), например, HashMap, не поддерживаются.
Операции над стримами могут выполняться как последовательно, так и параллельно.
Потоки не могут быть использованы повторно. Как только была вызвана какая-нибудь конечная операция, поток закрывается.
Кроме универсальных объектных существуют особые виды стримов для работы с примитивными типами данных int, long и double: IntStream, LongStream и DoubleStream. Эти примитивные стримы работают так же, как и обычные объектные, но со следующими отличиями:
* используют специализированные лямбды-выражения, например IntFunctional или IntPredicate вместо Function и Predicate;
* поддерживают дополнительные конечные операции sum(), average(), mapToObj().
Чем отличаются терминальные операторы от промежуточных?
Терминальный оператор является завершающим. Он в цепочке может быть только один. Он обрабатывает элемент и завершает работу Stream-a. Запускает работу всех промежуточных операторов.
Промежуточный – обрабатывает поступающие элементы и возвращает Stream.
Какие существуют способы создания стрима?
- Из коллекции:
Stream<String> fromCollection = Arrays.asList("x", "y", "z").stream();</String> - Из набора значений:
Stream<String> fromValues = Stream.of("x", "y", "z");</String> - Из массива:
Stream<String> fromArray = Arrays.stream(new String[]{"x", "y", "z"});</String> - Из файла (каждая строка в файле будет отдельным элементом в стриме):
Stream<String> fromFile = Files.lines(Paths.get("input.txt"));</String> - Из строки:
IntStream fromString = “0123456789”.chars(); - С помощью Stream.builder():
Stream<String> fromBuilder = Stream.builder().add("z").add("y").add("z").build();</String> - С помощью Stream.iterate() (бесконечный):
Stream<Integer> fromIterate = Stream.iterate(1, n -> n + 1);</Integer> - С помощью Stream.generate() (бесконечный):
Stream<String> fromGenerate = Stream.generate(() -> "0");</String>
В чем разница между Collection и Stream?
Коллекции позволяют работать с элементами по-отдельности, тогда как стримы так делать не позволяют, но вместо этого предоставляют возможность выполнять функции над данными как над одним целым.
Также стоит отметить важность самой концепции сущностей: Collection - это прежде всего воплощение Структуры Данных. Например, Set не просто хранит в себе элементы, он реализует идею множества с уникальными элементами, тогда как Stream, это прежде всего абстракция необходимая для реализации конвейера вычислений, собственно, поэтому, результатом работы конвейера являются те или иные Структуры Данных или же результаты проверок/поиска и т.п.
Что делает метод stream()?
stream() превращает коллекцию в поток данных.
С потоком данных можно работать в функциональном стиле и выполнять множество операций, таких как, сортировка, фильтрация, преобразование и не создавать при этом много копий коллекции. Также можно распараллелить задачи вызвав метод parallelStream(), а иначе нам бы пришлось вручную разбивать коллекцию на несколько и выполнять операции, а потом склеивать их.
Для чего нужен метод collect()?
Метод collect() собирает все элементы в список, множество или другую коллекцию, сгруппировывает элементы по какому-нибудь критерию, объединяет всё в строку и т.д.:
Некоторые методы Collectors:
1. toList() — собирает элементы в List: List<Integer> list = Stream.of(99, 2, 3).collect(Collectors.toList());
2. toSet() — cобирает элементы в множество: Set<Integer> set = Stream.of(99, 2, 3).collect(Collectors.toSet());
3. counting() — Подсчитывает количество элементов:
Long count = Stream.of("1", "2", "3", "4").collect(Collectors.counting());
4. joining() — cобирает элементы в одну строку. Дополнительно можно указать разделитель, а также префикс и суффикс для всей последовательности:
String a = Stream.of("s", "u" ,"p", "e", "r").collect(Collectors.joining());
System.out.println(a); // super
String b = Stream.of("s", "u", "p", "e", "r").collect(Collectors.joining("-"));
System.out.println(b); // s-u-p-e-r
String c = Stream.of("s", "u", "p", "e", "r").collect(Collectors.joining(" -> ", "[ ", " ]"));
System.out.println(c); // [ s -> u -> p -> e -> r ]
5. summingInt(ToIntFunction mapper), summingLong(ToLongFunction mapper), summingDouble(ToDoubleFunction mapper) — коллектор, который преобразовывает объекты в int/long/double и подсчитывает сумму.</Integer></Integer>
Для чего в стримах применяются методы forEach() и forEachOrdered()?
forEach() применяет функцию к каждому объекту стрима, порядок при параллельном выполнении не гарантируется;
forEachOrdered() применяет функцию к каждому объекту стрима с сохранением порядка элементов.
Какова цель метода filter()?
Метод filter() является промежуточной операцией принимающей предикат, который фильтрует все элементы, возвращая только те, что соответствуют условию.
Для чего в стримах предназначен метод limit()?
Метод limit() является промежуточной операцией, которая позволяет ограничить выборку определенным количеством первых элементов.
Для чего в стримах предназначен метод sorted()?
Метод sorted() является промежуточной операцией, которая позволяет сортировать значения либо в натуральном порядке, либо задавая Comparator.
Порядок элементов в исходной коллекции остается нетронутым - sorted() всего лишь создает его отсортированное представление.
Что делает метод map()?
Применяет операцию преобразования к каждому элементу потока. На выходе элементы могут иметь любой тип.
Какие конечные методы работы со стримами вы знаете?
- findFirst() возвращает первый элемент;
- findAny() возвращает любой подходящий элемент;
- collect() представление результатов в виде коллекций и других структур данных;
- count() возвращает количество элементов;
- anyMatch() возвращает true, если условие выполняется хотя бы для одного элемента;
- noneMatch() возвращает true, если условие не выполняется ни для одного элемента;
- allMatch() возвращает true, если условие выполняется для всех элементов;
- min() возвращает минимальный элемент, используя в качестве условия Comparator;
- max() возвращает максимальный элемент, используя в качестве условия Comparator;
- forEach() применяет функцию к каждому объекту (порядок при параллельном выполнении не гарантируется);
- forEachOrdered() применяет функцию к каждому объекту с сохранением порядка элементов;
- toArray() возвращает массив значений;
- reduce()позволяет выполнять агрегатные функции и возвращать один результат.
Для числовых стримов дополнительно доступны: - sum() возвращает сумму всех чисел;
- average() возвращает среднее арифметическое всех чисел.
Какие промежуточные методы работы со стримами вы знаете?
- filter() отфильтровывает записи, возвращая только записи, соответствующие условию;
- skip() позволяет пропустить определённое количество элементов в начале;
- distinct() возвращает стрим без дубликатов (для метода equals());
- map() преобразует каждый элемент;
- peek() возвращает тот же стрим, применяя к каждому элементу функцию;
- limit() позволяет ограничить выборку определенным количеством первых элементов;
- sorted() позволяет сортировать значения либо в натуральном порядке, либо задавая Comparator;
- mapToInt(), mapToDouble(), mapToLong() - аналоги map() возвращающие стрим числовых примитивов;
- flatMap(), flatMapToInt(), flatMapToDouble(), flatMapToLong() - похожи на map(), но могут создавать из одного элемента несколько.
Для числовых стримов дополнительно доступен метод mapToObj(), который преобразует числовой стрим обратно в объектный.
Параллельные Stream-ы
Стримы могут быть последовательными и параллельными. Операции над последовательными стримами выполняются в одном потоке процессора, над параллельными — используя несколько потоков процессора. Для создания параллельного потока из коллекции можно также использовать метод parallelStream() интерфейса Collection.
Чтобы сделать обычный последовательный стрим параллельным, надо вызвать у объекта Stream метод parallel(). Метод isParallel() позволяет узнать является ли стрим параллельным.
Какая имплементация находится под капотом у параллельных Stream-ов.
Параллельные стримы используют общий ForkJoinPool доступный через статический ForkJoinPool.commonPool() метод. При этом, если окружение не является многоядерным, то поток будет выполняться как последовательный. Фактически применение параллельных стримов сводится к тому, что данные в стримах будут разделены на части, каждая часть обрабатывается на отдельном ядре процессора, и в конце эти части соединяются, и над ними выполняются конечные операции.
Функциональные интерфейсы.
Функциональным считается интерфейс с одним не реализованным (абстрактным) методом. Могут помимо основного этого метода содержать также статические или дефолтные методы. То есть хоть в нем будет по сути не один метод, но это все равно будет считаться функциональным интерфейсом.
Чтобы точно определить интерфейс как функциональный, добавлена аннотация @FunctionalInterface, работающая по принципу @Override. Она обозначит замысел и не даст определить второй абстрактный метод в интерфейсе.
Базовые функциональные интерфейсы Java 8:
Predicate — функциональный интерфейс для проверки соблюдения некоторого условия. Если условие соблюдается, возвращает true, иначе — false.
Consumer — функциональный интерфейс, который принимает в качестве входного аргумента объект, совершает некоторые действия, но при этом ничего не возвращает.
Supplier — функциональный интерфейс, который не принимает никаких аргументов, но возвращает некоторый объект.
Function — этот функциональный интерфейс принимает аргумент одного типа и приводит его к объекту другого типа, который и возвращается как результат.
UnaryOperator — функциональный интерфейс, принимает в качестве параметра объект, выполняет над ним некоторые операции и возвращает результат операций в виде объекта того же типа.
Что такое default методы интерфейса и как вызвать это дефолтный метод из класса, который реализует этот интерфейс?
Чтобы определить дефолтный метод в функциональном интерфейсе (это значит, что он будет не абстрактным и иметь какую-то реализацию), нам нужно перед void поставить слово default, ну а дальше как в обычном методе написать, что делает этот метод. Чтобы же вызвать этот метод из класса, нужно использовать ключевое слово super.
interface Paper {
default void show() {
System.out.println(“default show()”);
}
} class Licence implements Paper {
public void show() {
Paper.super.show();
}
}
Что такое статический метод функционального интерфейса и как вызвать это статический метод из класса, который реализует этот интерфейс?
Статический метод похож на дефолтный, только отличается тем, что его нельзя переобределить в классе, имплементирующем этот интерфейс. Статические методы являются частью самого интерфейса.
interface Paper {
static void show() {
System.out.println(“static show()”);
}
} class Licence {
public void showPaper() {
Paper.show();
}
}
Как функциональные интерфейсы связаны со Stream?
Метод класса Stream — filter в качестве аргумента принимает Predicate и возвращает Stream только с теми элементами, которые удовлетворяют условию Predicate. В контексте Stream-а это означает, что он пропускает только те элементы, которые возвращают true при использовании их в методе test интерфейса Predicate.
Одним из методом в Stream, который использует функциональный интерфейс Consumer, является метод peek. Но так как метод peek работает с Consumer, модификации строк в Stream не произойдет, а сам peek вернет Stream с изначальными элементами: такими, какими они ему пришли.
Примером метода в Stream, использующего функциональный интерфейс Supplier, является generate, который генерирует бесконечную последовательность на основе переданного ему функционального интерфейса.
Пример метода в Stream c аргументом Function — метод map, который принимает элементы одного типа, что-то с ними делает и передает дальше, но это уже могут быть элементы другого типа.
В качестве метода, использующего UnaryOperator как аргумент - метод класса Stream — iterate.
Лямбды.
Лямбда представляет собой набор инструкций, которые можно выделить в отдельную переменную и затем многократно вызвать в различных местах программы.
Основу лямбда-выражения составляет лямбда-оператор, который представляет стрелку ->. Этот оператор разделяет лямбда-выражение на две части: левая часть содержит список параметров выражения, а правая, собственно, представляет тело лямбда-выражения, где выполняются все действия.
Лямбда-выражение не выполняется само по себе, а образует реализацию метода, определенного в функциональном интерфейсе. При этом важно, что функциональный интерфейс должен содержать только один единственный метод без реализации. По факту лямбда-выражения являются в некотором роде сокращенной формой внутренних анонимных классов, которые ранее применялись в Java.
Элементарные задачи на знание стримов:
Как вывести на экран 10 случайных чисел, используя forEach()?
(new Random())
.ints()
.limit(10)
.forEach(System.out::println);
Как можно вывести на экран уникальные квадраты чисел используя метод map()?
Stream
.of(1, 2, 3, 2, 1)
.map(s -> s * s)
.distinct()
.forEach(System.out::println);
Как вывести на экран количество пустых строк с помощью метода filter()?
System.out.println(
Stream
.of(“Hello”, “”, “, “, “world”, “!”)
.filter(String::isEmpty)
.count());
Как вывести на экран 10 случайных чисел в порядке возрастания?
(new Random())
.ints()
.limit(10)
.sorted()
.forEach(System.out::println);
Как найти максимальное число в наборе?
Stream
.of(5, 3, 4, 55, 2)
.mapToInt(a -> a)
.max()
.getAsInt(); //55
Как найти минимальное число в наборе?
Stream
.of(5, 3, 4, 55, 2)
.mapToInt(a -> a)
.min()
.getAsInt(); //2
Как получить сумму всех чисел в наборе?
Stream
.of(5, 3, 4, 55, 2)
.mapToInt()
.sum(); //69
Как получить среднее значение всех чисел?
Stream
.of(5, 3, 4, 55, 2)
.mapToInt(a -> a)
.average()
.getAsDouble(); //13.8