Стримы и функциональные интерфейсы. Функциональные Интерфейсы. Streams
Скачать 171.72 Kb.
|
Функциональные Интерфейсы. Streams 1) Функциональные интерфейсы, основные типы. Основные типы Функциональных интерфейсов: 2) Что такое лямбда выражение и ссылка на метод. К каким переменным можно обращаться внутри лямбды? Может ли лямбда-выражение быть в несколько строк? 3) Как они связаны с анонимным классом. Общее: Различия: Анонимные классы Как создать? Любой ли анонимный класс можно заменить на лямбду? Можно ли создать анонимный класс от String? 4) Что такое Стрим. Для чего нужен? Когда их лучше использовать? Чем Stream отличается от итератора? Сравнение стримов с коллекцией? 5) Какие бывают стримы (По разным критериям, например "конечные и бесконечные") 6) Терминальные и промежуточные методы. Что такое ленивая инициализация стрима? Приведи пример терминальной и промежуточной операции над стримом? В чем разница методов map и flatMap? В чем разница методов peek и forEach? В чем разница методов forEach и forEachOrdered? Можно ли конкатенировать стримы? если да, то каким методом? В каком случае нужно закрывать стрим? 7) Способы получения стрима. Дополнительно Может ли функциональный интерфейс содержать что-то кроме абстрактного метода? Может ли стрим использоваться повторно? Что такое функциональное программирование? плюсы и минусы Плюсы Минусы где применяется Декларати́вное программи́рование Objects (зачем нужен)? Все способы реализации функциональных интерфейсов В чем разница методов list() и walk()? В чем разница методов range и rangeClosed? Что такое Optional и зачем нужно? Что такое коллекторы? Что такое саплайер-поставщик? Разница между Comparator и Comparable? 1) Функциональные интерфейсы, основные типы. Функциональный интерфейс — это интерфейс, который содержит ровно один абстрактный метод, то есть описание метода без тела. Статические методы и методы по умолчанию при этом не в счёт, их в функциональном интерфейсе может быть сколько угодно. Для контроля правильности написания функционального интерфейса можно использовать аннотацию @FunctionalInterface. Основные типы Функциональных интерфейсов: ● Predicate Функциональный интерфейс Predicate Если оно соблюдается, то возвращается значение true. В качестве параметра лямбда-выражение принимает объект типа T. ● Consumer Consumer ● Function Функциональный интерфейс Function ● Supplier Supplier ● UnaryOperator UnaryOperator ● BinaryOperator BinaryOperator 2) Что такое лямбда выражение и ссылка на метод. Лямбда выражение - это реализация функционального интерфейса. Лямбда представляет набор инструкций, которые можно выделить в отдельную переменную и затем многократно вызвать в различных местах программы. Основу лямбда-выражения составляет лямбда-оператор, который представляет стрелку ->. Этот оператор разделяет лямбда-выражение на две части: левая часть содержит список параметров выражения, а правая собственно представляет тело лямбда-выражения, где выполняются все действия. Ссылка на метод — это реализация функционального интерфейса, описанная другим существующим методом. Если лямбда выражения вызывают только один существующий метод, лучше ссылаться на этот метод по его имени. Работают ссылки на методы при условии, что параметры вызываемого метода и параметры в лямбде совпадают. Ссылки на методы бывают четырех видов: Тип Пример Ссылка на статический метод ContainingClass::staticMethodName Ссылка на нестатический метод конкретного объекта containingObject::instanceMethodName Ссылка на нестатический метод любого объекта конкретного типа(при стриме об. этого типа) ContainingType::methodName Ссылка на конструктор ClassName::new ( ClassName не может быть абстрактным классом или интерфейсом.) К каким переменным можно обращаться внутри лямбды? Изнутри лямбда-выражения можно не только обращаться ко всем «видимым» переменным, но и вызывать те методы, к которым есть доступ, а именно: К переменные внутри метода (Лямбда выражения могут захватывать эффективно финализированные локальные переменные в области видимости ее объявления.) Доступ к переменным экземпляра Лямбда-выражения также могут обращаться к переменным экземпляра в объекте, создавшем лямбда.(изменять можно) Доступ к статическим переменным Лямбда-выражения Java также могут обращаться к статическим переменным.(изменять можно) К переменным интерфейса (с которым лямбда работает) Может ли лямбда-выражение быть в несколько строк? Да. Тело после -> должно обрамляться фигурными скобками {}, каждая строчка заканчиваться точкой с запятой. Если лямбда-выражение возвращает результат(и содержит более одной строки), ключевое слово return также требуется. 3) Как они связаны с анонимным классом. Лямбда выражения являются альтернативой анонимным классам. Но они не одинаковы. Лямбда-выражения появились в Java 8, как способ имплементации анонимных методов и, в некоторых случаях, как альтернатива анонимным классам. Общее: ⦁ Локальные переменные могут быть использованы только если они final или effective final. ⦁ Разрешается доступ к переменным класса и статическим переменным класса. ⦁ Они не должны выбрасывать больше исключений чем определено в throws метода функционального интерфейса. Различия: ⦁ Для анонимных классов ключевое слово this ссылается на сам класс. Для лямбда выражений на внешний класс. ⦁ Анонимные классы компилируются во внутренние классы. А лямбда выражения преобразуются в статические private методы класса, в котором они используют invokedynamic инструкцию. ⦁ Лямбда более эффективны, так как не надо загружать ещё один класс. Анонимные классы Анонимный класс – это класс без имени. Использование таких классов обычно обусловлено необходимостью однократного создания объекта класса, реализующего тот или иной абстрактный класс или интерфейс. Анонимный класс является подклассом существующего класса или реализацией интерфейса, т.е. анонимный класс всегда от кого-то наследуется. Поскольку анонимный класс не имеет имени, он не может иметь явный конструктор. Анонимные классы никогда не могут быть статическими, либо абстрактными, и всегда являются конечными классами. Анонимный класс может реализовать только один интерфейс. Кроме того, каждое объявление анонимного класса уникально. Как создать? Объявление такого класса выполняется одновременно с созданием его объекта посредством использования оператора new. Любой ли анонимный класс можно заменить на лямбду? Только анонимные классы, которые являются реализациями функциональных интерфейсов (например, Runnable, ActionListener, Comparator, Predicate), могут быть заменены выражением лямбда. Т.е. только те классы, которые реализуют 1 абстрактный метод. Можно ли создать анонимный класс от String? Нет, так как Анонимный класс является подклассом существующего класса или реализации интерфейса, т.е. АК всегда от кого-то наследуется, а класс String является final, т.е. от него нельзя наследоваться. 4) Что такое Стрим. Java Stream – это объект (поток данных), способный выполнять внутреннюю итерацию своих элементов. Это последовательность элементов, потенциально бесконечная, с возможностью применять к ней, сложные, поэтапные преобразования без цикла и условного оператора. Для чего нужен? Stream API — нужен для работы со структурами данных в функциональном стиле. С появлением Java 8 Stream API позволило программистам писать существенно короче то, что раньше занимало много строк кода, а именно — упростить работу с наборами данных, в частности, упростить операции фильтрации, сортировки и другие манипуляции с данными. Когда их лучше использовать? Стримы используются тогда, когда вам необходимо выполнить несколько промежуточных операций над данными, например коллекцией. В каком пакете находится Stream? Вся основная функциональность данного API сосредоточена в пакете java. util. stream. Чем Stream отличается от итератора? Порядок в обходе по итератору может быть задан и заранее определен. Стоимость доступа к элементам в стримах гораздо ниже. Протокол Iterator принципиально менее эффективен. Для получения каждого элемента требуется вызов двух методов. Кроме того, поскольку итераторы должны быть устойчивы к таким вещам, как вызов next() без hasNext() или hasNext() несколько раз без next(), оба этих метода обычно должны выполнять некоторую защитную кодировку, что увеличивает неэффективность. Сравнение стримов с коллекцией? Разница между Collection и Stream в том, что коллекции позволяют работать с элементами по-отдельности, тогда как поток (Stream) не позволяет. Например, с использованием коллекций, вы можете добавлять элементы, удалять, и вставлять в середину. - Потоки не хранят элементы. Элементы, используемые в потоках, могут храниться в коллекции, либо при необходимости могут быть напрямую сгенерированы. 5) Какие бывают стримы (По разным критериям, например "конечные и бесконечные") Последовательные и параллельные, конечные и бесконечные, объектные и примитивные. 1) Конечные и бесконечные Бесконечные - создают бесконечный поток данных, до срабатывания условия (создание через iterate и generate). Конечные стримы создаются методом stream(). 2) Последовательные и параллельные Стримы бывают последовательными (sequential) и параллельными (parallel). Последовательные выполняются только в текущем потоке. Параллельные используют общий пул ForkJoinPool.commonPool(). При этом элементы разбиваются (если это возможно) на несколько групп и обрабатываются в каждом потоке отдельно. Затем на нужном этапе объединяются в один, для предоставления конечного результата. Чтобы получить параллельный стрим, нужно либо вызвать метод parallelStream() интерфейса Collection вместо stream(), либо превратить обычный стрим в параллельный, вызвав промежуточный оператор parallel. 3) Объектные и примитивные Стримы по умолчанию объектные, но существуют специальные стримы для примитивов: LongStream() DoubleStream() IntStream() 6) Терминальные и промежуточные методы. Стрим состоит из источника - (элементы) -> промежуточных операторов - (элементы) -> терминального оператора. Операторы можно разделить на две группы: Промежуточные или КОНВЕЕРНЫЕ операторы(“intermediate”, ещё называют “lazy”) — обрабатывают поступающие элементы и возвращают стрим. Промежуточных операторов в цепочке обработки элементов может быть много. Терминальные (“terminal”, еще называют “eager”) — обрабатывают элементы и завершают работу стрима, так что терминальный оператор в цепочке может быть только один. У стрима может быть сколько угодно вызовов промежуточных операций и последним вызов конечной операции. При этом все промежуточные операции выполняются лениво и пока не будет вызвана конечная операция никаких действий на самом деле не происходит. Что такое ленивая инициализация стрима? Ленивая инициализация — это концепция отсрочки создания объекта до тех пор, пока объект не будет фактически впервые использован, а все промежуточные операции выполняются только тогда, когда есть терминальная операция. При правильном использовании это может привести к значительному повышению производительности. Приведи пример терминальной и промежуточной операции над стримом? Промежуточные операции concat - объединяет два потока distinct - возвращает поток, в котором имеются только уникальные данные с типом T dropWhile(predicate) - пропускает элементы, которые соответствуют условию в predicate, пока не попадется элемент, который не соответствует условию. Выбранные элементы возвращаются в виде потока filter(predicate) - фильтрует элементы в соответствии с условием в предикате limit - оставляет в потоке только maxSize элементов map - преобразует элементы типа T в элементы типа R и возвращает поток с элементами R flatMap - позволяет преобразовать элемент типа T в несколько элементов типа R и возвращает поток с элементами R skip - возвращает поток, в котором отсутствуют первые n элементов sorted - возвращает отсортированный поток sorted(comparator) - возвращает отсортированный поток (в соответствии с компаратором) takeWhile(predicate) - выбирает из потока элементы, пока они соответствуют условию в predicate. Выбранные элементы возвращаются в виде потока Терминальные операции void forEach(action) - для каждого элемента выполняется действие action collect(Collector super T,A,R> collector) - добавляет элементы в неизменяемый контейнер Optional Optional Object[] toArray() - возвращает массив из элементов потока Optional Optional В чем разница методов map и flatMap? map для каждого объекта в стриме возвращает по 1 объекту, потом преобразует все объекты в итоговый стрим. flatMap принимает функцию (которая преобразует каждое значение входного стрима в стрим), применяет ее к каждому элементу, и на выходе возвращает стрим с одним или несколькими элементами для каждого элемента входящего стрима. В чем разница методов peek и forEach? Оба применяют функцию к каждому элементу стрима, но peek возвращает стрим, а forEach нет, так как является терминальной операцией. В чем разница методов forEach и forEachOrdered? У forEach порядок при параллельном выполнении не гарантируется. Можно ли конкатенировать стримы? если да, то каким методом? Объединение с помощью метода Stream.concat(stream1, stream2); В каком случае нужно закрывать стрим? Потоки имеют метод close () и реализуют AutoCloseable, но практически все экземпляры потоков на самом деле не нужно закрывать после использования. Как правило, закрывать будут только те потоки, источником которых является канал ввода-вывода (например, те, что возвращены Files.lines (Path, Charset)). Большинство потоков поддерживаются коллекциями, массивами или генерирующими функциями, которые не требуют специального управления ресурсами. (Если поток требует закрытия, он может быть объявлен как ресурс в инструкции try-with-resources.) 7) Способы получения стрима. Стримы создаются на основе каких-либо источников, например классов из java.util.Collection. Потоки не могут быть использованы повторно. Как только была вызвана какая-нибудь конечная операция, поток закрывается. Кроме универсальных объектных существуют особые виды стримов для работы с примитивными типами данных int, long и double: IntStream, LongStream и DoubleStream.Эти примитивные стримы работают так же, как и обычные объектные, но со следующими отличиями: используют специализированные лямбда-выражения, например, IntFunction или IntPredicate вместо Function и Predicate; поддерживают дополнительные конечные операции sum(), average(), mapToObj(). 1) Из коллекции: Stream fromCollection = Arrays.asList("x", "y", "z").stream(); Стрим из List: list.stream() Стрим из Map: map.entrySet().stream() 2) Из набора значений: Stream fromValues = Stream.of("x", "y", "z"); 3) Из массива: Stream fromArray = Arrays.stream(new String[]{"x", "y", "z"}); 4) Из файла (каждая строка в файле будет отдельным элементом в стриме): Stream fromFile = Files.lines(Paths.get("input.txt")); 5) Из строки: IntStream fromString = "0123456789".chars(); 6) С помощью Stream.builder(): Stream fromBuilder = Stream.builder().add("z").add("y").add("z").build(); 7) С помощью Stream.iterate() (бесконечный): Stream fromIterate = Stream.iterate(1, n -> n + 1); 8) С помощью Stream.generate() (бесконечный): Stream fromGenerate = Stream.generate(() -> "0"); 9) Создание паралельного стрима collection.parallelStream() Stream stream = collection.parallelStream(); Дополнительно Может ли функциональный интерфейс содержать что-то кроме абстрактного метода? Он может содержать статические методы, методы по умолчанию, и переопределенные методы класса Object (в том числе в качестве абстрактных). Может ли стрим использоваться повторно? Потоки не могут быть использованы повторно. Как только вы называете какую-нибудь терминальную операция, поток закрывается. Чтобы избежать этого, мы должны создать новую цепь для каждой терминальной операции. Если вы по какой-то причине хотите несколько копий стрима, рекомендуется использовать Supplier Что такое функциональное программирование? Функциональное программирование — это парадигма декларативного программирования, в которой акцент программного кода сводится к использованию функций, а не инструкций. При таком подходе каждая отдельная функция используется как объект. А это значит, что функцию можно: * присвоить переменным * передать в виде аргумента другой функции * возвратить в качестве результата от другой функции В функциональном программировании программы создаются путем последовательного применения функций, а не инструкций. Каждая из этих функций принимает входное значение и возвращает согласующееся с ним выходное значение, не изменяясь и не подвергаясь воздействию со стороны состояния программы. Для таких функций предусмотрено выполнение только одной операции, если же требуется реализовать сложный процесс, то используется уже композиция функций, связанных последовательно. В процессе ФП мы создаем код, состоящий из множества модулей, поскольку функции в нем могут повторно использоваться в разных частях программы путем вызова, передачи в качестве параметров или возвращения. Чистые функции — функциональное программирование использует чистые функции. Это функции, которые не изменяются, дают надёжные результаты и всегда дают одинаковый результат для одного и того же ввода. Они не вызывают неожиданных результатов или побочных эффектов и абсолютно предсказуемы независимо от внешнего кода. плюсы и минусы Плюсы Один из самых явных плюсов функционального программирование — это высокоуровневые абстракции, которые скрывают большое количество подробностей таких рутинных операций, как, например, итерирование. За счет этого код получается короче, и, как следствие, гарантирует меньшее количество ошибок, которые могут быть допущены. Более легкая отладка за счет использования «чистых» функций и неизменных данных Отложенные вычисления (Ленивая оценка) - функциональная программа вычисляется только при необходимости Модульность - «чистые» функции можно использовать в разных областях одного кода Облегченное параллельное программирование Простота понимания чистых функций, они зависят только от входных данных и не меняют состояние программы Слабая связь функции с данными, которыми она оперирует позволяет избежать побочных эффектов при выполнении функций. Минусы Неизменяемость и рекурсия иногда могут приводить к снижению производительности Многие объекты создаются в момент кодирования, потому его сложно поддерживать Объединение чистых функций с другими операциями ввода-вывода где применяется Например при создании искусственного интеллекта , в высоконагруженных вычислительных систем, DataScience, BigData. В тех проектах, когда программа теряет связь с физическим миром. Декларати́вное программи́рование — парадигма программирования , в которой задается спецификация решения задачи, то есть описывается ожидаемый результат, а не способ его получения. Противоположностью декларативного является императивное программирование , при котором на том или ином уровне детализации требуется описание последовательности шагов для решения задачи. Objects (зачем нужен)? Этот класс состоит из static служебных методов для работы с объектами. Содержит null безопасные или null толерантные методы для вычисления хэш-кода объекта, возврата строки для объекта и сравнения двух объектов. Все способы реализации функциональных интерфейсов ● С помощью анонимного класса ● С помощью лямбда - выражения. ● С помощью ссылки на метод ● С помощью обычного класса, путем имплементации В чем разница методов list() и walk()? Files.list() возвращает стрим файлов(Path) в указанной директории. Files.walk() возвращает стрим файлов(Path) в указанной директории и поддиректориях. В чем разница методов range и rangeClosed? IntStream range(int startInclusive, int endExclusive) возвращает последовательный упорядоченный IntStream от startInclusive (включительно) до endExclusive (исключительно) с шагом приращения 1. rangeClosed включает endExclusive. Что такое Optional и зачем нужно? Задачей этого класса является предоставление решений на уровне типа-обертки для обеспечения удобства обработки возможных null-значений. Optional фактически обертывает результат операции. После выполнения операции с помощью метода get() объекта Optional мы можем получить его значение Что такое коллекторы? Коллекторы – это объекты, разработанные для какой-либо агрегации потоковых данных. Поток, пройдя через цепочку отображений, фильтров и других не терминальных операций, по итогу представляет из себя поток объектов. Их необходимо либо собрать воедино, либо выбрать какой-то один, а может быть, и вообще получить новый объект на основе каких-то характеристик. В этой точке и могут быть применены коллекторы. Что такое саплайер-поставщик? Интерфейс Supplier используется тогда, когда на вход не передаются значения, но необходимо вернуть результат Разница между Comparator и Comparable? Интерфейс Comparable используется только для сравнения объектов класса, в котором данный интерфейс реализован. Т.е. interface Comparable определяет логику сравнения объекта определенного ссылочного типа внутри своей реализации (по правилам разработчика). Интерфейс Comparator представляет отдельную реализацию и ее можно использовать многократно и с различными классами. Т.е. interface Comparator позволяет создавать объекты, которые будут управлять процессом сравнения (например, при сортировках). В отличие от Comparable, Comparator может дополнительно разрешать сравнение нулевых аргументов. Контракт Comparator-а * отрицательный int (первый объект меньше) * положительный int (первый объект больший) * ноль = объекты равны Если класс по какой-то причине не может реализовать интерфейс Comparable, или же просто нужен другой вариант сравнения, используется интерфейс Comparator. Интерфейс содержит метод int compare(T o1, T o2), который должен быть реализован классом, реализующим компаратор. Метод compare возвращает числовое значение - если оно отрицательное, то объект o1 предшествует объекту o2, иначе - наоборот. А если метод возвращает ноль, то объекты равны. Для применения интерфейса нам необходимо создать класс компаратора. |