Stream Flashcards

1
Q

Что такое Stream?

A

Интерфейс java.util.Stream представляет собой последовательность элементов, над которой можно производить различные операции.
Операции над стримами бывают или промежуточными (intermediate) или конечными (terminal). Конечные операции возвращают результат определенного типа, а промежуточные операции возвращают тот же стрим. Таким образом вы можете строить цепочки из несколько операций над одним и тем же стримом.
У стрима может быть сколько угодно вызовов промежуточных операций и последним вызов конечной операции. При этом все промежуточные операции выполняются лениво и пока не будет вызвана конечная операция никаких действий на самом деле не происходит (похоже на создание объекта Thread или Runnable, без вызова start()).
Стримы создаются на основе каких-либо источников, например Collection.
Ассоциативные массивы (maps), например, HashMap, не поддерживаются.
Операции над стримами могут выполняться как последовательно, так и параллельно.
Потоки не могут быть использованы повторно. Как только была вызвана какая-нибудь конечная операция, поток закрывается.
Кроме универсальных объектных существуют особые виды стримов для работы с примитивными типами данных int, long и double: IntStream, LongStream и DoubleStream. Эти примитивные стримы работают так же, как и обычные объектные, но со следующими отличиями:
* используют специализированные лямбды-выражения, например IntFunctional или IntPredicate вместо Function и Predicate;
* поддерживают дополнительные конечные операции sum(), average(), mapToObj().

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
2
Q

Чем отличаются терминальные операторы от промежуточных?

A

Терминальный оператор является завершающим. Он в цепочке может быть только один. Он обрабатывает элемент и завершает работу Stream-a. Запускает работу всех промежуточных операторов.
Промежуточный – обрабатывает поступающие элементы и возвращает Stream.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
3
Q

Какие существуют способы создания стрима?

A
  1. Из коллекции:
    Stream<String> fromCollection = Arrays.asList("x", "y", "z").stream();</String>
  2. Из набора значений:
    Stream<String> fromValues = Stream.of("x", "y", "z");</String>
  3. Из массива:
    Stream<String> fromArray = Arrays.stream(new String[]{"x", "y", "z"});</String>
  4. Из файла (каждая строка в файле будет отдельным элементом в стриме):
    Stream<String> fromFile = Files.lines(Paths.get("input.txt"));</String>
  5. Из строки:
    IntStream fromString = “0123456789”.chars();
  6. С помощью Stream.builder():
    Stream<String> fromBuilder = Stream.builder().add("z").add("y").add("z").build();</String>
  7. С помощью Stream.iterate() (бесконечный):
    Stream<Integer> fromIterate = Stream.iterate(1, n -> n + 1);</Integer>
  8. С помощью Stream.generate() (бесконечный):
    Stream<String> fromGenerate = Stream.generate(() -> "0");</String>
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
4
Q

В чем разница между Collection и Stream?

A

Коллекции позволяют работать с элементами по-отдельности, тогда как стримы так делать не позволяют, но вместо этого предоставляют возможность выполнять функции над данными как над одним целым.
Также стоит отметить важность самой концепции сущностей: Collection - это прежде всего воплощение Структуры Данных. Например, Set не просто хранит в себе элементы, он реализует идею множества с уникальными элементами, тогда как Stream, это прежде всего абстракция необходимая для реализации конвейера вычислений, собственно, поэтому, результатом работы конвейера являются те или иные Структуры Данных или же результаты проверок/поиска и т.п.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
5
Q

Что делает метод stream()?

A

stream() превращает коллекцию в поток данных.
С потоком данных можно работать в функциональном стиле и выполнять множество операций, таких как, сортировка, фильтрация, преобразование и не создавать при этом много копий коллекции. Также можно распараллелить задачи вызвав метод parallelStream(), а иначе нам бы пришлось вручную разбивать коллекцию на несколько и выполнять операции, а потом склеивать их.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
6
Q

Для чего нужен метод collect()?

A

Метод 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>

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
7
Q

Для чего в стримах применяются методы forEach() и forEachOrdered()?

A

forEach() применяет функцию к каждому объекту стрима, порядок при параллельном выполнении не гарантируется;
forEachOrdered() применяет функцию к каждому объекту стрима с сохранением порядка элементов.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
8
Q

Какова цель метода filter()?

A

Метод filter() является промежуточной операцией принимающей предикат, который фильтрует все элементы, возвращая только те, что соответствуют условию.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Для чего в стримах предназначен метод limit()?

A

Метод limit() является промежуточной операцией, которая позволяет ограничить выборку определенным количеством первых элементов.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
10
Q

Для чего в стримах предназначен метод sorted()?

A

Метод sorted() является промежуточной операцией, которая позволяет сортировать значения либо в натуральном порядке, либо задавая Comparator.
Порядок элементов в исходной коллекции остается нетронутым - sorted() всего лишь создает его отсортированное представление.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
11
Q

Что делает метод map()?

A

Применяет операцию преобразования к каждому элементу потока. На выходе элементы могут иметь любой тип.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Какие конечные методы работы со стримами вы знаете?

A
  • findFirst() возвращает первый элемент;
  • findAny() возвращает любой подходящий элемент;
  • collect() представление результатов в виде коллекций и других структур данных;
  • count() возвращает количество элементов;
  • anyMatch() возвращает true, если условие выполняется хотя бы для одного элемента;
  • noneMatch() возвращает true, если условие не выполняется ни для одного элемента;
  • allMatch() возвращает true, если условие выполняется для всех элементов;
  • min() возвращает минимальный элемент, используя в качестве условия Comparator;
  • max() возвращает максимальный элемент, используя в качестве условия Comparator;
  • forEach() применяет функцию к каждому объекту (порядок при параллельном выполнении не гарантируется);
  • forEachOrdered() применяет функцию к каждому объекту с сохранением порядка элементов;
  • toArray() возвращает массив значений;
  • reduce()позволяет выполнять агрегатные функции и возвращать один результат.
    Для числовых стримов дополнительно доступны:
  • sum() возвращает сумму всех чисел;
  • average() возвращает среднее арифметическое всех чисел.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
13
Q

Какие промежуточные методы работы со стримами вы знаете?

A
  • filter() отфильтровывает записи, возвращая только записи, соответствующие условию;
  • skip() позволяет пропустить определённое количество элементов в начале;
  • distinct() возвращает стрим без дубликатов (для метода equals());
  • map() преобразует каждый элемент;
  • peek() возвращает тот же стрим, применяя к каждому элементу функцию;
  • limit() позволяет ограничить выборку определенным количеством первых элементов;
  • sorted() позволяет сортировать значения либо в натуральном порядке, либо задавая Comparator;
  • mapToInt(), mapToDouble(), mapToLong() - аналоги map() возвращающие стрим числовых примитивов;
  • flatMap(), flatMapToInt(), flatMapToDouble(), flatMapToLong() - похожи на map(), но могут создавать из одного элемента несколько.
    Для числовых стримов дополнительно доступен метод mapToObj(), который преобразует числовой стрим обратно в объектный.
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
14
Q

Параллельные Stream-ы

A

Стримы могут быть последовательными и параллельными. Операции над последовательными стримами выполняются в одном потоке процессора, над параллельными — используя несколько потоков процессора. Для создания параллельного потока из коллекции можно также использовать метод parallelStream() интерфейса Collection.
Чтобы сделать обычный последовательный стрим параллельным, надо вызвать у объекта Stream метод parallel(). Метод isParallel() позволяет узнать является ли стрим параллельным.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
15
Q

Какая имплементация находится под капотом у параллельных Stream-ов.

A

Параллельные стримы используют общий ForkJoinPool доступный через статический ForkJoinPool.commonPool() метод. При этом, если окружение не является многоядерным, то поток будет выполняться как последовательный. Фактически применение параллельных стримов сводится к тому, что данные в стримах будут разделены на части, каждая часть обрабатывается на отдельном ядре процессора, и в конце эти части соединяются, и над ними выполняются конечные операции.

How well did you know this?
1
Not at all
2
3
4
5
Perfectly
16
Q

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

A

Функциональным считается интерфейс с одним не реализованным (абстрактным) методом. Могут помимо основного этого метода содержать также статические или дефолтные методы. То есть хоть в нем будет по сути не один метод, но это все равно будет считаться функциональным интерфейсом.
Чтобы точно определить интерфейс как функциональный, добавлена аннотация @FunctionalInterface, работающая по принципу @Override. Она обозначит замысел и не даст определить второй абстрактный метод в интерфейсе.
Базовые функциональные интерфейсы Java 8:
Predicate — функциональный интерфейс для проверки соблюдения некоторого условия. Если условие соблюдается, возвращает true, иначе — false.
Consumer — функциональный интерфейс, который принимает в качестве входного аргумента объект, совершает некоторые действия, но при этом ничего не возвращает.
Supplier — функциональный интерфейс, который не принимает никаких аргументов, но возвращает некоторый объект.
Function — этот функциональный интерфейс принимает аргумент одного типа и приводит его к объекту другого типа, который и возвращается как результат.
UnaryOperator — функциональный интерфейс, принимает в качестве параметра объект, выполняет над ним некоторые операции и возвращает результат операций в виде объекта того же типа.

17
Q

Что такое default методы интерфейса и как вызвать это дефолтный метод из класса, который реализует этот интерфейс?

A

Чтобы определить дефолтный метод в функциональном интерфейсе (это значит, что он будет не абстрактным и иметь какую-то реализацию), нам нужно перед void поставить слово default, ну а дальше как в обычном методе написать, что делает этот метод. Чтобы же вызвать этот метод из класса, нужно использовать ключевое слово super.
interface Paper {
default void show() {
System.out.println(“default show()”);
}
} class Licence implements Paper {
public void show() {
Paper.super.show();
}
}

18
Q

Что такое статический метод функционального интерфейса и как вызвать это статический метод из класса, который реализует этот интерфейс?

A

Статический метод похож на дефолтный, только отличается тем, что его нельзя переобределить в классе, имплементирующем этот интерфейс. Статические методы являются частью самого интерфейса.
interface Paper {
static void show() {
System.out.println(“static show()”);
}
} class Licence {
public void showPaper() {
Paper.show();
}
}

19
Q

Как функциональные интерфейсы связаны со Stream?

A

Метод класса 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.

20
Q

Лямбды.

A

Лямбда представляет собой набор инструкций, которые можно выделить в отдельную переменную и затем многократно вызвать в различных местах программы.
Основу лямбда-выражения составляет лямбда-оператор, который представляет стрелку ->. Этот оператор разделяет лямбда-выражение на две части: левая часть содержит список параметров выражения, а правая, собственно, представляет тело лямбда-выражения, где выполняются все действия.
Лямбда-выражение не выполняется само по себе, а образует реализацию метода, определенного в функциональном интерфейсе. При этом важно, что функциональный интерфейс должен содержать только один единственный метод без реализации. По факту лямбда-выражения являются в некотором роде сокращенной формой внутренних анонимных классов, которые ранее применялись в Java.

21
Q

Элементарные задачи на знание стримов:

A

Как вывести на экран 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