Главная страница

Чем является Stream в контексте Java


Скачать 103.73 Kb.
НазваниеЧем является Stream в контексте Java
АнкорStreamApi
Дата11.09.2022
Размер103.73 Kb.
Формат файлаdocx
Имя файла7_StreamAPI.docx
ТипПрограмма
#671145

Чем является Stream в контексте Java?



Stream API — это новый способ работать со структурами данных в функциональном стиле. Stream (поток) API (описание способов, которыми одна компьютерная программа может взаимодействовать с другой программой) — это по своей сути поток данных. Сам термин "поток" довольно размыт в программировании в целом и в Java в частности.

Для чего нужен?

С появлением Java 8 Stream API позволило программистам писать существенно короче то, что раньше занимало много строк кода, а именно — упростить работу с наборами данных, в частности, упростить операции фильтрации, сортировки и другие манипуляции с данными. Если у вас промежуточных операций нет, часто можно и нужно обойтись без стрима, иначе код будет сложнее чем без потока.

Какие бывают стримы?

1)    конечные и бесконечные;

Бесконечные (iterate, generate) - создают бесконечный поток данных, до срабатывания условия.

2) последовательные и параллельные;

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

Чтобы получить параллельный стрим, нужно либо вызвать метод parallelStream() вместо stream(), либо превратить обычный стрим в параллельный, вызвав промежуточный оператор parallel.

3)    объектные и примитивные;

Специальные типы стримов для примитивных типов:

- LongStream(),

- DoubleStream(),

- IntStream().

 

Что такое ленивая инициализация стрима?

Ленивая инициализация — это концепция отсрочки создания объекта до тех пор, пока объект не будет фактически впервые использован. При правильном использовании это может привести к значительному повышению производительности. Все промежуточные операции выполняются только тогда, когда есть терминальная операция.

Что возвращают промежуточные операции над стримом?

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

Что такое терминальная операция?

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

Две терминальные операции в одном выражении?

нет

Что возвращают промежуточные операции над стримом?

Промежуточные операции возвращают трансформированный стрим. Конечные или терминальные операции возвращают конкретный результат.

В каком пакете находится Stream?

Вся основная функциональность данного API сосредоточена в пакете java. util. stream.

Чем Stream отличается от итератора?

- Порядок в обходе итератора может быть задан и заранее определен.

- Стоимость (затраты мощностей процессора) доступа к элементам в стримах гораздо ниже.

Сравнение стримов с коллекцией?

Разница между коллекцией (Collection) данных и потоком (Stream) из новой JDK8 в том, что коллекции позволяют работать с элементами по-отдельности, тогда как поток (Stream) не позволяет. Например, с использованием коллекций, вы можете добавлять элементы, удалять, и вставлять в середину. Stream не позволяет манипулировать с отдельными данными, но позволяет выполнять функции над данными как над одним целым.

Из каких частей состоит использование стримов?

- создание стрима;

- промежуточные операции (или их отсутствие);

- терминальная операция (запускает весь процесс вычисления);

  Откуда можно получить стрим?

- из коллекций

- из значений

- из массива

- из файла

- из строки

- с помощью Stream.builder()

- бесконечный стрим с помощью Stream.iterate()

- бесконечный стрим с помощью Stream.generate()

В каком случае нужно закрывать стрим?

Только потоки, источником которых является канал ввода-вывода, например Files.lines(Path, Charset) должны быть закрыты. Остальные реализуют AutoClosable.

Можно ли конкатенировать стримы? если да, то каким методом?

Объединение с помощью метода .concat(stream1, stream2);

Можно ли получить пустой стрим?

Пустой стрим: Stream.empty()

Как получить стрим из массива?

Arrays.stream(массив)

Что такое коллекторы?

Коллекторы – это объекты, разработанные для какой-либо агрегации потоковых данных. Поток, пройдя через цепочку отображений, фильтров и других не терминальных операций, по итогу представляет из себя поток объектов. Их необходимо либо собрать воедино, либо выбрать какой-то один, а может быть, и вообще получить новый информационный объект на основе каких-то характеристик. В этой точке и могут быть применены коллекторы.

В чем разница map и flatMap?

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

Методы peek и forEach - в чем разница?

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

В чем разница между forEach и forEachOrdered?

У forEach порядок при параллельном выполнении не гарантируется.

Разница методов. list() и walk()?

Files.list() возвращает массив файлов в указанной директории, Files.walk() возвращает поток файлов в указанной директории и субдиректориях.

Что такое саплайер-поставщик?

Интерфейс Supplier используется тогда, когда на вход не передаются значения, но необходимо вернуть результат.

Как получить стрим диапазона чисел?

IntStream.range(0, 10) .collect(Collectors.toList());

В чем разница методов range и rangeClosed?

В range(1, 10) в диапазон не включено число 10, а в rangeClosed(1, 10) число 10 включено.

Может ли стрим использоваться повторно?

Нет, каждый стрим одноразовый.

Для чего нужны параллельные стримы?

Распараллеливание потоков позволяет задействовать несколько ядер процессора (если целевая машина многоядерная) и тем самым может повысить производительность и ускорить вычисления. В то же время говорить, что применение параллельных потоков на многоядерных машинах однозначно повысит производительность - не совсем корректно. В каждом конкретном случае надо проверять и тестировать. Чтобы сделать обычный последовательный поток параллельным, надо вызвать у объекта Stream метод parallel(). Кроме того, можно также использовать метод parallelStream() интерфейса Collection для создания параллельного потока из коллекции. В то же время если рабочая машина не является многоядерной, то поток будет выполняться как последовательный.

Приведи пример терминальной и промежуточной операции над стримом?

Stream concat​(Stream a, Stream b): объединяет два потока. Промежуточная операция

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

Stream dropWhile​(Predicate predicate): пропускает элементы, которые соответствуют условию в predicate, пока не попадется элемент, который не соответствует условию. Выбранные элементы возвращаются в виде потока. Промежуточная операция.

Stream filter(Predicate predicate): фильтрует элементы в соответствии с условием в предикате. Промежуточная операция

Stream limit(long maxSize): оставляет в потоке только maxSize элементов. Промежуточная операция

  Stream map(Function mapper): преобразует элементы типа T в элементы типа R и возвращает поток с элементами R. Промежуточная операция

Stream flatMap(Function> mapper): позволяет преобразовать элемент типа T в несколько элементов типа R и возвращает поток с элементами R. Промежуточная операция

Stream skip(long n): возвращает поток, в котором отсутствуют первые n элементов. Промежуточная операция.

Stream sorted(): возвращает отсортированный поток. Промежуточная операция.

Stream sorted(Comparator comparator): возвращает отсортированный в соответствии с компаратором поток. Промежуточная операция.

Stream takeWhile​(Predicate predicate): выбирает из потока элементы, пока они соответствуют условию в predicate. Выбранные элементы возвращаются в виде потока. Промежуточная операция.

Object[] toArray(): возвращает массив из элементов потока. Терминальная операция.

boolean allMatch(Predicate predicate): возвращает true, если все элементы потока удовлетворяют условию в предикате. Терминальная операция

boolean anyMatch(Predicate predicate): возвращает true, если хоть один элемент потока удовлетворяют условию в предикате. Терминальная операция

boolean noneMatch(Predicate predicate): возвращает true, если ни один из элементов в потоке не удовлетворяет условию в предикате. Терминальная операция

Optional max(Comparator comparator): возвращает максимальный элемент из потока. Для сравнения элементов применяется компаратор comparator. Терминальная операция

Optional min(Comparator comparator): возвращает минимальный элемент из потока. Для сравнения элементов применяется компаратор comparator. Терминальная операция

R collect(Collector collector): добавляет элементы в неизменяемый контейнер с типом R. T представляет тип данных из вызывающего потока, а A - тип данных в контейнере. Терминальная операция

long count(): возвращает количество элементов в потоке. Терминальная операция.

Optional findFirst(): возвращает первый элемент из потока. Терминальная операция

Optional findAny(): возвращает первый попавшийся элемент из потока. Терминальная операция

void forEach(Consumer action): для каждого элемента выполняется действие action. Терминальная операция.

Лямбда:

 

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

Лямбда-выражение или просто лямбда в Java — упрощённая запись анонимного класса, реализующего функциональный интерфейс

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

ShapeService rectangleService = new ShapeService() {

          @Override

          public double perimeter(double a, double b) {

         return 2 * (a + b); }};

Эволюция в лямбда начинается с того, что опускается конструктор анонимного класса и имя метода интерфейса. Так как метод единственный в интерфейсе, то и его имя можно не упоминать. Параметры метода отделяются от тела метода оператором «стрелка»:

ShapeService rectangleService = (double a, double b) -> {

          return 2 * (a + b);};

Если тело метода состоит из одного оператора, то и фигурные скобки можно опустить.

ShapeService rectangleService = (double a, double b) -> 2 * (a + b);

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

ShapeService rectangleService = (a, b) -> 2 * (a + b);

Применение лямбда-выражений становится возможным только для функциональных интерфейсов из-за единственного абстрактного метода, так как исчезает необходимость явно указывать имя метода.

Может ли лямбда-выражение быть в несколько строк?

Да. В данном случае тело после -> должно обрамляться фигурными скобками {}, каждая строчка заканчиваться точкой с запятой. Если лямбда-выражение возвращает результат, ключевое слово

К каким переменным и как можно обращаться в теле лямбда-выражений?

Изнутри лямбда-выражения можно не только обращаться ко всем «видимым» переменным, но и вызывать те методы, к которым есть доступ, а именно:

- переменные внутри метода (эффективно-финальные),

- static переменные класса,

- переменные интерфейса (с которым лямбда работает).

Что такое Method References (ссылка на метод)?

Если лямбда выражения вызывают только один существующий метод, лучше ссылаться на этот метод по его имени. Ссылки на методы (Method References) – это компактные лямбда выражения для методов, у которых уже есть имя. Например:

Consumer consumer = str -> System.out.println(str);

можно переписать с помощью method references:

Consumer consumer = System.out::println;

В каком виде передается Method References (ссылка на метод)?

- имя_класса:: имя_стат_метода (для статического метода);

- объект_класса:: имя_метода (для метода экземпляра);

- название_класса:: new (для конструктора).

Анонимные классы

Анонимные классы, как создать, где применяются, особенно как создать экземпляр?

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

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

Поскольку анонимный класс не имеет имени, он не может иметь явный конструктор. Также к анонимному классу невозможно обратиться извне объявляющего его выражения, за исключением неявного обращения посредством объектной ссылки на суперкласс или интерфейс. Анонимные классы никогда не могут быть статическими, либо абстрактными, и всегда являются конечными классами. Кроме того, каждое объявление анонимного класса уникально.

Объявление такого класса выполняется одновременно с созданием его объекта посредством использования оператора new. Синтаксис: после оператора new вызывается конструктор класса (в случае обычного наследования) или указывается абстрактный класс или интерфейс, после которого следуют пустые круглые скобки (в случае реализации), затем в фигурных скобках описывается реализация. После фигурных скобок тела анонимного класса обязательно ставить точку с запятой.

MonitoringSystem generalModule = new MonitoringSystem() {};

Можно ли заменить любой анонимный класс выражением лямбда?

Ответ – нет. Из-а их отличий. Лямбда может реализовать только один функциональный интерфейс. Анонимный класс сколько угодно абстрактных методов.

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

Что такое функциональные интерфейсы? Для чего нужны?

Функциональный интерфейс в Java – это интерфейс, который содержит только 1 абстрактный метод. Основное назначение – использование в лямбда-выражениях и ссылках на методы.

Какой аннотацией помечается функциональный интерфейс?

Предназначение @FunctionalInterface — сообщить компилятору, что данный интерфейс функциональный и должен содержать не более одного метода. Если же в интерфейсе с данной аннотацией более одного не реализованного (абстрактного) метода, компилятор не пропустит данный интерфейс, так как будет воспринимать его как ошибочный код. Интерфейсы и без данной аннотации могут считаться функциональными и будут работать, а @FunctionalInterface является не более чем дополнительной страховкой.

Может ли функциональный интерфейс содержать что-то кроме абстрактного метода? Что такое default методы?

Функциональный интерфейс может содержать любое количество методов по умолчанию (default) или статических методов.

default – реализованные методы в функциональных интерфейсах. При наследовании интерфейса можно переопределить эти методы или же оставить всё как есть (оставить логику по умолчанию).

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

Где находятся функциональные интерфейсы?

Многие функциональные интерфейсы, предоставляемые Java8, находятся в пакете java.util.function.
Сколько дефолтных методов и статических методов, статических полей в функциональном интерфейсе?

Ограничений по количеству методов и полей в функциональном интерфейсе нет (кроме абстрактного метода).

Основные типы функциональных интерфейсов.

Predicate — функциональный интерфейс для проверки соблюдения некоторого условия. Если условие соблюдается, возвращает true, иначе — false. Содержит метод boolean test(T t).

Consumer (с англ. — “потребитель”) — принимает в качестве входного аргумента объект типа T, совершает некоторые действия, но при этом ничего не возвращает. Содержит метод void accept(T t).

Supplier (с англ. — поставщик) — не принимает никаких аргументов, но возвращает некоторый объект типа T. Содержит метод T get().

Function —принимает аргумент T и приводит его к объекту типа R, который и возвращается как результат. Содержит метод R apply(T t);

UnaryOperator — принимает в качестве параметра объект типа T, выполняет над ним некоторые операции и возвращает результат операций в виде объекта того же типа T. Содержит метод T apply(T t).

BinaryOperator — принимает в качестве параметра два объекта типа T, выполняет над ними бинарную операцию и возвращает ее результат также в виде объекта типа T. Содержит метод T apply(T t1, T t2).

Расскажите про Comparator и Comparable?

Интерфейс Comparable используется только для сравнения объектов класса, в котором данный интерфейс реализован. Т.е. interface Comparable определяет логику сравнения объекта определенного ссылочного типа внутри своей реализации (по правилам разработчика).

Comparator представляет отдельную реализацию и ее можно использовать многократно и с различными классами. Т.е. interface Comparator позволяет создавать объекты, которые будут управлять процессом сравнения (например, при сортировках).

Все способы реализации фун.инт: 4 штуки:

- ссылка на метод

- лямбда

- через анонимный класс

- через обычную имплементацию в классе.

Отличие BinaryOperator от Function

Function(функция) используется для преобразования входного параметра или двух параметров (для BiFunction) в какое-либо значение, тип значение может не совпадать с типом входных параметров.

BinaryOperator это разновидность Function, в которых входные и выходные обобщенные параметры должны совпадать. Если заглянуть в пакет java.util.function, то можно заметить, что UnaryOperator расширяет Function, а BinaryOperator расширяет BiFunction.

Все способы реализации функционального интерфейса?

В Java 8 функциональные интерфейсы могут быть представлены с использованием лямбда-выражений, ссылок на методы и ссылок на конструкторы.

Разное 

Императивный vs декларативный подход.

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

Императивное программирование — это парадигма программирования, в которой задаётся последовательность действий, необходимых для получения результата.

Функциональное программирование- плюсы минусы, где применяется.

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


написать администратору сайта