Стримы.JAVA Flashcards

1
Q

Что такое Stream API? Для чего нужны стримы?

A

Интерфейс java.util.Stream представляет собой последовательность элементов, над которой можно производить различные операции.

Нужны для упрощения работы с наборами данных, в частности, упростить операции фильтрации, сортировки и другие манипуляции с данными.
IntStream.of(50, 60, 70, 80, 90, 100, 110, 120).filter(x -> x < 90).map(x -> x + 10).limit(3).forEach(System.out::print);

Cоздаем экземпляр Stream

Пустой стрим: Stream.empty()
Стрим из List: list.stream()
Стрим из Map: map.entrySet().stream()
Стрим из массива: Arrays.stream(array)
Стрим из указанных элементов: Stream.of("1", "2", "3")
Стрим из BufferedReader с помощью метода lines (); нужно закрывать close ().
Промежуточные (“intermediate”, “lazy”) — обрабатывают поступающие элементы и возвращают стрим. Может быть много, а может и не быть ни одной.
Терминальные (“terminal”, ещё называют “eager”) — обрабатывают элементы и завершают работу стрима, может быть только один.
Важные моменты:

Обработка не начнётся до тех пор, пока не будет вызван терминальный оператор. list.stream().filter(s -> s > 5) (не возьмёт ни единого элемента из списка);
Экземпляр стрима нельзя использовать более одного раза;
Кроме универсальных объектных существуют особые виды стримов для работы с примитивными типами данных int, long и double: IntStream, LongStream и DoubleStream. Эти примитивные стримыработают так же, как и обычные объектные, но со следующими отличиями:

используют специализированные лямбда-выражения, например, IntFunction или IntPredicate вместо Function и Predicate;
поддерживают дополнительные конечные операции sum(), average(), mapToObj()

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

Почему Stream называют ленивым?

A

Ленивое программирование - технология, которая позволяет вам отсрочить вычисление кода до тех пор, пока не понадобится его результирующее значение.

Блок обработки – промежуточные операции не выполняются, пока не вызовется терминальная.

Stream в Java называют ленивым потоком (lazy stream), потому что он не выполняет операции на элементах коллекции сразу, а откладывает их до момента, когда они станут необходимы для вычисления конечного результата.

Это означает, что когда мы вызываем методы filter(), map() или reduce() на потоке, они не выполняются сразу на каждом элементе коллекции, а лишь создают новый поток с операциями, которые нужно выполнить. Таким образом, поток можно рассматривать как последовательность задач, которые будут выполнены только тогда, когда это станет необходимым.

Преимущество ленивости Stream заключается в том, что это позволяет оптимизировать вычисления и экономить ресурсы, особенно в случаях, когда не все элементы коллекции необходимы для вычисления конечного результата. Например, если мы фильтруем коллекцию, чтобы получить только первые 10 элементов, Stream будет выполнять операции только на первых 10 элементах коллекции, а не на всех.

Кроме того, ленивость Stream позволяет использовать его с бесконечными коллекциями данных, такими как поток случайных чисел или последовательность Фибоначчи. В таких случаях Stream выполняет операции только на том количестве элементов, которое необходимо для вычисления конечного результата, не загружая память всей коллекции данных.

В целом, ленивость Stream позволяет упростить и оптимизировать обработку данных, особенно при работе с большими коллекциями или потоками данных.

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

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

A

Из коллекции: Stream<String> fromCollection = Arrays.asList("x", "y", "z").stream();
Из набора значений: Stream<String> fromValues = Stream.of("x", "y", "z");
Из массива: Stream<String> fromArray = Arrays.stream(new String[]{"x", "y", "z"});
Из файла (каждая строка в файле будет отдельным элементом в стриме): Stream<String> fromFile = Files.lines(Paths.get("input.txt"));
Из строки: IntStream fromString = "0123456789".chars();
С помощью Stream.builder(): Stream<String> fromBuilder = Stream.builder().add("z").add("y").add("z").build();
С помощью Stream.iterate() (бесконечный): Stream<Integer> fromIterate = Stream.iterate(1, n -> n + 1);
С помощью Stream.generate() (бесконечный): Stream<String> fromGenerate = Stream.generate(() -> "0");</String></Integer></String></String></String></String></String>

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

Как из коллекции создать стрим?

A

Stream<String> fromCollection = Arrays.asList("x", "y", "z").stream();</String>

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

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

A

concat(Stream<? extends T> a, Stream<? extends T> b): объединяет два потока.

distinct(): возвращает поток, в котором имеются только уникальные данные с типом T.

dropWhile(Predicate<? super T> predicate): пропускает элементы, которые соответствуют условию в predicate, пока не попадется элемент, который не соответствует условию. Выбранные элементы возвращаются в виде потока.

filter(Predicate<? super T> predicate): фильтрует элементы в соответствии с условием в предикате.

limit(long maxSize): оставляет в потоке только maxSize элементов.

map(Function<? super T,? extends R> mapper): преобразует элементы типа T в элементы типа R и возвращает поток с элементами R.

flatMap(Function<? super T, ? extends Stream<? extends R» mapper): позволяет преобразовать элемент типа T в несколько элементов типа R и возвращает поток с элементами R.

skip(long n): возвращает поток, в котором отсутствуют первые n элементов.

sorted(): возвращает отсортированный поток.

sorted(Comparator<? super T> comparator): возвращает отсортированный в соответствии с компаратором поток.

takeWhile(Predicate<? super T> predicate): выбирает из потока элементы, пока они соответствуют условию в predicate. Выбранные элементы возвращаются в виде потока.

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

Расскажите про метод peek() быстрый взгляд.

A

Stream<T> peek(Consumer<? super T> action);</T>

peek() - возвращает стрим, дополнительно выполняя указанное действие над каждым элементом.

integerStream.peek(System.out::println) - Позволяет подсмотреть какие элементы летают на данном этапе с помощью System.out::println

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

Расскажите про метод map() маппинг из одного в другое

A

Отображение или маппинг позволяет задать функцию преобразования одного объекта в другой, то есть получить из элемента одного типа элемент другого типа.

принимает Function.

<R> Stream<R> map(Function<? super T, ? extends R> mapper);

map(n -> n.toString())
</R></R>

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

Расскажите про метод flatMap(). плоский маппинг

A

Плоское отображение выполняется тогда, когда из одного элемента нужно получить несколько.

Stream

.of("H e l l o", "w o r l d !")

.flatMap((p) -> Arrays.stream(p.split(" ")))

.toArray(String[]::new);//["H", "e", "l", "l", "o", "w", "o", "r", "l", "d", "!"]

Например, в примере выше мы выводим название телефона и его цену. Но что, если мы хотим установить для каждого телефона цену со скидкой и цену без скидки. То есть из одного объекта Phone нам надо получить два объекта с информацией, например, в виде строки. Дляэтого применим flatMap:

Stream<Phone> phoneStream = Stream.of(new Phone("iPhone 6 S", 54000), new Phone("Lumia 950", 45000),new Phone("Samsung Galaxy S 6", 40000));</Phone>

phoneStream

.flatMap(p->Stream.of(String.format("название: %s  цена без скидки: %d", p.getName(), p.getPrice()), String.format("название: %s  цена со скидкой: %d", p.getName(), p.getPrice()-(int)(p.getPrice()*0.1))))

.forEach(s->System.out.println(s));
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
9
Q

Чем отличаются методы map() и flatMap().

A

Разница заключается в том, что операция map() создает одно выходное значение для каждого входного значения, тогда как операция flatMap() создает произвольное число (ноль или больше) значений для каждого входного значения.

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

Расскажите про метод filter()

A

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

Stream<String> citiesStream = Stream.of("Париж", "Лондон", "Мадрид","Берлин", "Брюссель");</String>

citiesStream.filter(s->s.length()==6).forEach(s->System.out.println(s));

чтобы отбросить null с потока данных .filter(Objects::nonNull)

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

Расскажите про метод limit()

A

limit(long n) применяется для выборки первых n элементов потоков. Этот метод также возвращает модифицированный поток, в котором не более n элементов.

Stream<String> phoneStream = Stream.of("iPhone 6 S", "Lumia 950", "Samsung Galaxy S 6", "LG G 4","Nexus 7");</String>

phoneStream.skip(1)

.limit(2)

.forEach(s->System.out.println(s)); // Lumia 950 Samsung Galaxy S 6
How well did you know this?
1
Not at all
2
3
4
5
Perfectly
12
Q

Расскажите про метод skip()

A

skip(long n) используется для пропуска n элементов. Этот метод возвращает новый поток, в которомпропущены первые n элементов.

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

Расскажите про метод sorted()

A

Для простой сортировки по возрастанию применяется метод sorted(). Подходит только для сортировки тех объектов, которые реализуют интерфейс Comparable.

Если же у нас классы объектов не реализуют этот интерфейс или мы хотим создать какую-то свою логику сортировки, то мы можем использовать другую версию метода sorted(), которая в качестве параметра принимает компаратор.

class Phone{

private String name;

private String company;

private int price;

public Phone(String name,

String comp, int price){

    this.name=name;

    this.company=comp;

    this.price = price;

}

public String getName() { 

return name; }

public int getPrice() { 

return price; }

public String getCompany() {

return company; }

}

import java.util.Comparator;

import java.util.stream.Stream;

public class Program {

public static void main(String[] args) {

    Stream<Phone> phoneStream = Stream.of(new Phone(\"iPhone

X", "Apple", 600),

        new Phone(\"Pixel 2\", \"Google\", 500),

        new Phone(\"iPhone 8\", \"Apple\",450),

        new Phone(\"Nokia 9\", \"HMD Global\",150),

        new Phone(\"Galaxy S9\", \"Samsung\", 300));

     

    phoneStream.sorted(new PhoneComparator())

            .forEach(p->System.out.printf(\"\%\s (\%\s) - \%\d \n\", 

                    p.getName(), p.getCompany(), p.getPrice()));

     

} 

}

class PhoneComparator implements Comparator<Phone>{</Phone>

  public int compare(Phone a, Phone b){

          return

a.getName().toUpperCase().compareTo(b.getName().toUpperCase());

}

}

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

Расскажите про метод distinct()

A

“особый” возвращает только ункальные элементы в виде потока

Stream<String> people = Stream.of(\"Tom\", \"Bob\", \"Sam\", \"Tom\", \"Alice\", \"Kate\", \"Sam\");</String>

people.distinct().forEach(p -> System.out.println(p));

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

Какие терминальные методы в стримах вы знаете?

A

Терминальные методы (“terminal”) - методы обрабатывающие элементы потока и
завершающие его работу. Терминальный метод в потоке может быть только один. Именно
вызов терминального метода и запускает процесс выполнения цепочки промежуточных
методов.

forEach (принимает Consumer) например System.out::println (покажет все элементы, которые остались в стриме)
Optional<T> findAny() Вернет Optional если элемент в потоке есть, или пустой если нет
Optional<T> findFirst() Вернет Optional с первым элементом в потоке есть, или пустой если нет
allMatch (принимает предикат) позволяет удостовериться, удовлетворяют ли все элементы стрима определенному условию
min () возвращает минимальный элемент из стрима
count () возвращает количество элементов, оставшееся в стриме
sum () 0
collect () собирает элементы стрима в новое хранилище, например список. Куда может собрать смотри список Collectors:
groupingBy группирует по параметрам
averagingInt среднеарифметическое какого то параметра
summarizingInt дает кол-во элементов, суммму, мин, ср.арифм, макс значения
reduce () – позволяет вычислить свертку элементов стрима (результат применения некоторого бинарного оператора к паре элементов из стрима, пока от стрима не останется один единсттвенный элемент) свёртка — это математическая операция, применённая к двум функциям f и g, порождающая третью функцию, которая иногда может рассматриваться как модифицированная версия одной из первоначальных
Optional<T> max(Comparator<? super T> comparator) Вернет максимальный элемент из потока данных
Optional<T> min(Comparator<? super T> comparator) Вернет минимальный элемент из потока данных</T></T></T></T>

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

Расскажите про метод collect()

A

собирает элементы стрима в новое хранилище, например список

Эта функция представляет объект Collector, который определен в пакете java.util.stream. Java уже предоставляет ряд встроенных функций, определенных в классе Collectors:

toList(): преобразование к типу List
toSet(): преобразование к типу Set
toMap(): преобразование к типу Map
groupingBy() - разделяет коллекцию на несколько частей и возвращает Map<N, List<T>>;
summingInt(), summingDouble(), summingLong() - возвращает сумму;
List<String> filteredPhones = phones.stream()</String></T>

            .filter(s->s.length()<10)

            .collect(Collectors.toList());
17
Q

Расскажите про метод reduce() уменьшить

A

Позволяет выполнять какое-то действие на всей коллекции и возвращать один результат вычислим произведение набора чисел:

Stream<Integer> numbersStream = Stream.of(1,2,3,4,5,6);</Integer>

    Optional<Integer> result = numbersStream.reduce((x,y)->x*y);

    System.out.println(result.get()); // 720
18
Q

Расскажите про класс Collectors и его методы.

A

В Java 8 в классе Collectors реализовано несколько распространённых коллекторов:

toList(), toCollection(), toSet() - представляют стрим в виде списка, коллекции или множества;
toConcurrentMap(), toMap() - позволяют преобразовать стрим в Map;
averagingInt(), averagingDouble(), averagingLong() - возвращают среднее значение;
summingInt(), summingDouble(), summingLong() - возвращает сумму;
summarizingInt(), summarizingDouble(), summarizingLong() - возвращают SummaryStatistics сразными агрегатными значениями;
partitioningBy() - разделяет коллекцию на две части по соответствию условию и возвращает их как Map<Boolean, List>;
groupingBy() - разделяет коллекцию на несколько частей и возвращает Map<N, List<T>>;
mapping() - дополнительные преобразования значений для сложных Collector-ов.
Так же существует возможность создания собственного коллектора через Collector.of():</T>

Collector<String, List<String>, List<String>> toList = Collector.of(</String></String>

ArrayList::new,

List::add,

(l1, l2) -> { l1.addAll(l2); return l1; }

);

19
Q

Расскажите о параллельной обработке в Java 8.

A

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

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

Для создания параллельного потока из коллекции можно также использовать метод parallelStream() интерфейса Collection.

Чтобы сделать обычный последовательный стрим параллельным, надо вызвать у объекта Stream метод parallel(). Метод isParallel() позволяет узнать является ли стрим параллельным.

С помощью, методов parallel() и sequential() можно определять какие операции могут быть параллельными, а какие только последовательными. Так же из любого последовательного стрима можно сделать параллельный и наоборот:

collection

.stream()

.peek(…) // операция последовательна

.parallel()

.map(…) // операция может выполняться параллельно,

.sequential()

.reduce(…) // операция снова последовательна

Как правило, элементы передаются в стрим в том же порядке, в котором они определены в источнике данных. При работе с параллельными стримами система сохраняет порядок следования элементов.

Исключение составляет метод forEach(), который может выводить элементы в произвольном порядке. И чтобы сохранить порядок следования, необходимо применять метод forEachOrdered().

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

Размер данных - чем больше данных, тем сложнее сначала разделять данные, а потом их соединять.
Количество ядер процессора. Теоретически, чем больше ядер в компьютере, тем быстрее программа будет работать. Если на машине одно ядро, нет смысла применять параллельные потоки.
Чем проще структура данных, с которой работает поток, тем быстрее будут происходитьоперации. Например, данные из ArrayList легко использовать, так как структура данной коллекциипредполагает последовательность несвязанных данных. А вот коллекция типа LinkedList - нелучший вариант, так как в последовательном списке все элементы связаны с предыдущими/последующими. И такие данные трудно распараллелить.
Над данными примитивных типов операции будут производиться быстрее, чем надобъектами классов.
Крайне не рекомендуется использовать параллельные стримы для скольких-нибудь долгих операций (например, сетевых соединений), так как все параллельные стримы работают c одним ForkJoinPool, то такие долгие операции могут остановить работу всех параллельных стримов в JVM из-за отсутствия доступных потоков в пуле, т.е. параллельные стримы стоит использовать лишь для коротких операций, где счет идет на миллисекунды, но не для тех где счет может идти на секунды и минуты;
Сохранение порядка в параллельных стримах увеличивает издержки при выполнении и если порядок не важен, то имеется возможность отключить его сохранение и тем самым увеличить производительность, использовав промежуточную операцию unordered():
collection.parallelStream()

.sorted()

.unordered()

.collect(Collectors.toList());
20
Q

Что такое IntStream и DoubleStream?

A

Кроме универсальных объектных существуют особые виды стримов для работы с примитивными типами данных int, long и double: IntStream, LongStream и DoubleStream. Эти примитивные стримы работают так же, как и обычные объектные, но со следующими отличиями:

используют специализированные лямбда-выражения, например, IntFunction или IntPredicate вместо Function и Predicate;
поддерживают дополнительные конечные операции sum(), average(), mapToObj()

Возможность преобразования потоков

Для примитивных специализаций Stream существует особые промежуточные методы по
преобразованию одного типа потока в другой. Преобразование возможно только по восходящему
преобразованию.

IntStream -> LongStream -> DoubleStream

asIntStream() Преобразование в IntStream
asLongStream() Преобразование в LongStream
asDoubleStream() Преобразование в DoubleStream

21
Q

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

A

Коллекции позволяют работать с элементами по-отдельности, тогда как стримы так делать не позволяют, но вместо этого предоставляют возможность выполнять функции над данными как над одним целым.

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

22
Q

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

A

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

23
Q

Для чего в стримах предназначены методы map() и mapToInt(), mapToDouble(), mapToLong()?

A

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

mapToInt(), mapToDouble(), mapToLong() - аналоги map(), возвращающие соответствующий числовой стрим (то есть стрим из числовых примитивов):

Stream
.of(“12”, “22”, “4”, “444”, “123”)
.mapToInt(Integer::parseInt)
.toArray(); //[12, 22, 4, 444, 123]
Кроме того, map() может принимать в себя функциональный интерфейс Functional.