билеты модуль 7. 1 Функциональные интерфейсы, основные типы
Скачать 184 Kb.
|
16) Что такое стримы? Для чего они нужны? Когда их лучше использовать? Стримы избавляют программистов от написания стереотипного кода всякий раз, когда нужно сделать что-то с набором элементов. То есть благодаря стримам не приходится думать о деталях реализации. Есть и другие [плюсы: Стримы поддерживают один из основных принципов хорошего проектирования — слабую связанность (low coupling). Чем меньше класс знает про другие классы — тем лучше. Алгоритму сортировки не должно быть важно, ч то конкретно он сортирует. Это и делают стримы. С помощью стримов операции с коллекциями проще распараллелить: в императивном подходе для этого бы понадобился минимум ещё один цикл. Стримы позволяют уменьшить число побочных эффектов: методы Stream API не меняют исходные коллекции. Со Stream API лаконично записываются сложные алгоритмы обработки данных. Stream API любит неизменяемые данные. Если вы хотите поменять существующие структуры данных, а не создать новые, вам нужно что-то другое. Посмотрите в сторону новых стандартных методов (например, List.replaceAll). Stream API [любит независимые данные. Если для получения результата вам нужно использовать одновременно несколько элементов из входного набора, без сторонних библиотек будет очень коряво. Но библиотеки вроде StreamEx часто решают эту проблему. Stream API любит решать одну задачу за проход. Если вы хотите в один обход данных решить несколько разных задач, готовьтесь писать свои коллекторы. И не факт, что это вообще получится. Stream API не любит проверяемые исключения. Вам будет не очень удобно кидать их из операций Stream API. Опять же есть библиотеки, которые пытаются это облегчить (скажем, jOOλ), но я бы рекомендовал отказываться от проверяемых исключений. В стандартном Stream API не хватает некоторых операций, которые очень нужны. Например, takeWhile, появится только в Java 9. Может оказаться, что вы хотите чего-то вполне разумного и несложного, но сделать это не получится. Опять же, стоит заметить, что библиотеки вроде jOOλ и StreamEx решают большинство таких проблем. 17) Любую ли лямбду можно заменить на анонимный класс? В Java 8 можно любой анонимный класс, который реализует один абстрактный метод заменить на лямбду выражение, если анонимный класс реализует два абстрактных метода, то заменить на лямбду будет невозможно. Если вы проект перевели на Java 8, то такие измения безопасные. Idea дает возможность поменять все анонимные классы на лямбды. 19) [императивный vs [декларативный [подход [Императивный стиль Это такой стиль программирования, при котором вы описываете, как добиться желаемого результата. Например я пишу: — поставь сковородку на огонь; — возьми два яйца (куриных); — нанеси удар ножом по каждому; — вылей содержимое на сковородку; — выкинь скорлупу; … Это что ни на есть декларативный стиль, но при этом с примесью императивного. [Декларативный стиль Такой стиль, в котором вы описываете, какой именно результат вам нужен. Тут я просто пишу: — приготовь яичницу И получатель такого сообщения уже сам разбирается, какие шаги для этого надо предпринять. Почему пример из части про императивность на самом деле с примесью декларативности? Ну хотя бы потому что получатель должен знать, что такое сковорода и яйца. Императивный подход (как): Я вижу, что тот угловой столик свободен. Мы пойдём туда и сядем там. Декларативный подход (что): Столик для двоих, пожалуйста. Императивный подход означает то, как вы займёте место. Вы должны перечислить все шаги этого процесса. Декларативный же подход заявляет, что вам нужен столик на двоих 20) Что такое default методы в интерфейсе и для чего они были введены? [Default-методы появились Java 8. Default-метод — это метод, который реализуется прямо в интерфейсе, его помечают ключевым словом default. default-методы упрощают рефакторинг — а именно, добавление новых методов. До Java 8 все методы в интерфейсах были абстрактными. К чему это вело? К тому, что при добавлении нового метода в интерфейс приходилось править все классы, реализующие интерфейс — реализовывать метод в этих классах. Это было неудобно. А в Java 8 (в классы ядра) захотели ввести новые методы в старые интерфейсы. Так что ввели ключевое слово default и эти методы сделали default. Например, в интерфейсе java.lang.Iterable появились новые default-методы forEach() и spliterator(): какой метод унаследует класс, реализующий два интерфейса, если оба из них содержат default-методы с одинаковыми именами. Чтобы не было неопределенности (и чтобы скомпилировался код), мы обязаны переопределить в Kentavr метод sleep() , причем можно просто вызвать в нем метод sleep() любого из интерфейсов — Man либо Animal, указав через точку и super, чей именно метод нужен: 21) К каким переменным есть доступ из лямбда-выражения? [Доступ к переменным внешней области действия из лямбда-выражения очень схож к доступу из анонимных объектов. Можно ссылаться на: 1) неизменяемые (effectively final - не обязательно помеченные как final) локальные переменные; 2) поля класса; 3) статические переменные. К методам по умолчанию реализуемого функционального интерфейса обращаться внутри лямбда-выражения запрещено. effectively final локальные переменные В в лямбда-выражениях стоит использовать внешние (относительно выражения) неизменяемые значения, а не внешние переменные, значение и внутреннее состояние которых могут меняться. Под внешними неизменяемыми значениями, соответственно, подразумеваются effectively final локальные переменные и поля примитивных типов, а также effectively final объекты, внутреннее состояние которых не будет меняться. Связано это с тем, что Streams и лямбда-выражения проектировались из расчета на их многопоточное использование. 22) Любой анонимный класс можно заменить на лямбду? В Java 8 можно любой анонимный класс, который реализует один абстрактный метод заменить на лямбду выражение, если анонимный класс реализует два абстрактных метода, то заменить на лямбду будет невозможно. 23) 24) Отличие [BinaryOperator от Function. BinaryOperator возвращает тип данных тот же над которым производились действия. Функциональный интерфейс Function 25) Что такое ленивая инициализация стрима? Отложенная (ленивая) инициализация (англ. ... Lazy initialization) — приём в программировании, когда некоторая ресурсоёмкая операция (создание объекта, вычисление значения) выполняется непосредственно перед тем, как будет использован её результат. Ленивая инициализация-это оптимизация производительности, при которой вы откладываете (потенциально дорогостоящее) создание объекта до тех пор, пока оно вам действительно не понадобится. Секрет «ленивости» Stream в том, что каждый раз, когда вы используете Stream, он соединяет несколько промежуточных операций и присоединяет конечную операцию в конце. Такие методы, как map () и filter (), являются промежуточными операциями, и при их вызове немедленно возвращается другой объект Stream. Для таких методов, как reduced () и findFirst (), они являются конечными операциями, а реальные операции выполняются при их вызове для получения требуемых значений. 26) Две терминальные операции в одном выражении? Терминальная операция это, то что запускает стрим и в тоже самое время она получает результат, а значит она должна быть одна 27) Что такое терминальная операция? Это операции, которые как бы «запускают» наш стрим. Мы можем создать стрим и добавить в него любое количество промежуточных операций, но они не будут выполнены пока не будут добавлена терминальная операция. Выше мы уже применяли одну из самых популярных операций — forEach(Consumer В нее попадают все прошедшие через стрим объекты и обрабатываются в соответствие с тем алгоритмом, что будет указан в Consumer. 28) Что возвращают промежуточные операции над стримом? Промежуточные (“intermediate”, ещё называют “lazy”) операции — обрабатывают поступающие элементы и возвращают стрим. Промежуточных операторов в цепочке обработки элементов может быть много Промежуточные операции следует воспринимать как «отложенные», т.е. они не меняют сами данные, а только задают правила их изменения. А терминальные как раз инициируют всю цепочку преобразований и возвращают модифицированные данные. 29) Для чего нужны параллельные стримы? Stream API предоставляет очень простой механизм для выполнения операций над потоком параллельно: входной поток разбивается на части, если это возможно, и каждая такая часть обрабатывается параллельно с остальными, в раздельных нитях. Кроме последовательных потоков Stream API поддерживает параллельные потоки. Распараллеливание потоков позволяет задействовать несколько ядер процессора (если целевая машина многоядерная) и тем самым может повысить производительность и ускорить вычисления. В то же время говорить, что применение параллельных потоков на многоядерных машинах однозначно повысит производительность - не совсем корректно. В каждом конкретном случае надо проверять и тестировать. есть 3 [минуса 1) если у вас есть какое-то количество потоков, обрабатывающих запросы пользователей и в каждом потоке выполняются какие-либо операции над parallelStream(), эти потоки будут вынуждены ждать друг друга 2) уследить за нежелательными эффектами от влияния нитей друг на друга становится ещё сложнее. 3) параллельность обработки вообще не гарантируется и зависит от источника данных. А прирост производительности зависит от его способности корректно разделить набор данных на независимые блоки. 30) Что такое анонимный класс. [Анонимный класс ([anonymous class) - это локальный класс без имени. Используется тогда, когда нужно переопределить метод класса или интерфейса. 31) Что такое [ФИ [функциональный интерфейс и для чего он нужен и зачем были добавлены? Если интерфейс в Java содержит один и только один абстрактный метод, то он называется функциональным. Этот единственный метод определяет назначение интерфейса. Это интерфейс, котрый определяет сторого один метод. аннотанация @FunctionalInterface введена для обазначения интерфейса, функциональным, это анотанация используется для того, чтобы избежать случайного добавления абстрактных методов в функциональном интерфейсе. Она не обязательна, но является хорошей практикой чистого кода [ФИ позволяют нам использовать лямбда выражения для создания экземпляра таких интерфейсоф Интерфейс Runnable является одним из самых популярных, с одним методом run(). интерфейс может содержать сколько угодно default методов и при этом оставатьсяя функциональным, потому, что default методы не абстрактные 32) Какой аннотацией помечается функциональный интерфейс? аннотанация @FunctionalInterface 33) Сколько дефолтных методов и статических методов, сртатических полей в интерфейсе? Сколько хочешь, статик методы переопределять нельзя. Статик поля возможны, но это плохая практика. 34) Где находятся функциональные интерфейсы? В Java все стандартные функциональные интерфейсы лежат в пакете java.util.function 35) [СВОЙСТВА [ФИ Перечислить основные семейства функ.интерфейсов? Делятся на 7 семейств 1) [Consumer - потребители, те кто принимают(объект типа Т, совершают некотрые действия) но не возвращают в замен. Его подвиды IntConsumer, LongConsumer, DoubleConsumer они есть потому, что дженерики не могут парметризоваться примитивами. 2) [Supplir - поставщики, они не принимают ни какое значение, а просто возращают какоето значение типа Т. 3) [Predicate - приниает выражение какого то типа (для проверки некоторого условия) а наружу выдает булевское значение. есть для примитивов, есть булевские 4) Function - принимает аргумент типа Т и приводит его к объекту типа R, который и возвращается как результат, в общем случаее типы разные. Бывает принимают два параметра. могут стоять примитивы 5) Operator - это частный случай функции, когда на вход подается значение одного и того же типа. Унарный оператор принимает один параметр а Бинарный два параметра отдельные интерфейсы над лонгами и даблами 6) BinaryOperator 7) UnaryOperator 36) [ФИ Функциональные интерфейсы что они принимают и что возвращают? 1) [Consumer - потребители, те кто принимают (объект типа Т, совершают некотрые действия) но не возвращают в замен. Его подвиды IntConsumer, LongConsumer, DoubleConsumer они есть потому, что дженерики не могут парметризоваться примитивами. 2) [Supplir - поставщики, они не принимают ни какое значение, а просто возращают как оето значение типа Т. 3) Predicate - приниает выражение (для проверки некоторого условия) какого то типа а наружу выдает булевское значение. есть для примитивов, есть булевские 4) Function - принимает аргумент типа Т и приводит его к объекту типа R, который и возвращается как результат, в общем случаее типы разные. 5) Operator - это частный случай функции, когда на вход подается значение одного и того же типа. Унарный оператор принимает один параметр а Бинарный два параметра отдельные интерфейсы над лонгами и даблами 6) BinaryOperator 7) UnaryOperator 37) Какие есть способы [инстацировать функциональные интерфейсы (анонимный класс, лямбда выражения, ссылки на методы)? Функциональные интерфейсы можно инcтанцировать тремя способами 1) можно завести именнованный или анонимный класс, но это громоздко 2) можно использовать лямбда выражение. Обявляем имена параметра и тело метода sqware = x-> {return x * x}; обязательно точка с запятой. Если тело метода из одного метода, то скобки можно опустить а если есть ретерн то добавляем скобки вопрос к каким переменным и как можно обращатся в лямбда выражении 1) к параметрам лямбды, а также свободно объявлять и использовать любые переменные х -> x * x; 2) к полям(переменным) того класса в нутри котого объявлена лямбда, можно как читать, так и писать. IntSupplier sequense = () -> counter++; 3) к переменным которые объявлены внутри метода где объявлена лямбда. Но есть ограничения переменные должны быть эффективно финальные т.е. значение им должно присвоено ровно один раз до объявления лямбды, после чего оно менятся уже не может.(Типа мы написали final)int bonus = 10; IntUnaryOperator bonusAdder = (x) -> x + bonus; !!! Лямбдам нельзя присваивать новые значения переменным содержащимся в ее методе. Для обхода этого ограниечения используют трюк с обходом массива единичной длины. 3) с помощью ссылки на метод ToIntFunction Consumer |