Файл: Зачем нужно функциональное программирование, где оно применяется и в чем его отличие от программирования в стиле ооп или в процедурном стиле.docx

ВУЗ: Не указан

Категория: Не указан

Дисциплина: Не указана

Добавлен: 29.10.2023

Просмотров: 18

Скачиваний: 1

ВНИМАНИЕ! Если данный файл нарушает Ваши авторские права, то обязательно сообщите нам.

  1. Зачем нужно функциональное программирование, где оно применяется и в чем его отличие от программирования в стиле ООП или в процедурном стиле

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

Плюсы

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

  • Программист сфокусирован на высокоуровневом “что требуется”, а не на низкоуровневом “как делать”.

  • Увеличение продуктивности и качества работы программиста.

Минусы

  • ФП не имеет механизма создания абстракций, потому-то основано на математических правилах.

  • У ФП есть проблемы с инкапсуляцией,

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




  1. Что такое инстанцирование?

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

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

Функциональный интерфейс в Java – это интерфейс, который содержит только 1 абстрактный метод. Функциональный интерфейс может содержать любое количество методов с уровнем доступа default или static.

Основное назначение – использование в лямбда выражениях и ссылках на метод.

  1. Зачем нужна аннотация @FunctionalInterface? Обязательна ли она?

Аннотация не обязательна, но при её наличии код не скомпилируется, если будет больше или меньше, чем 1 абстрактный метод. Рекомендуется добавлять @FunctionalInterface. Это позволит использовать интерфейс в лямбда выражениях, не остерегаясь того, что кто-то добавит в интерфейс новый абстрактный метод и он перестанет быть функциональным

  1. Аннотация @FunctionalInterface: что будет если написать два метода под ней?

предупреждение компилятора об ошибке

  1. Что такое default методы в интерфейсе и для чего они были введены?

default методы – не абстрактные и не требуют обязательного определения в реализации интерфейса. Вызвать default-метод интерфейса в реализуемом его классе можно используя ключевое слово super вместе с именем интерфейса.


  • Если у вас есть интерфейс, который другие люди внедрили, то если вы добавите новый метод в интерфейс, все существующие реализации будут нарушены. Добавив новый метод с реализацией default, вы остаётесь совместимым с исходным кодом с существующими реализациями.



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

Интерфейсы могут содержать поля, так же, как и обычные классы, но с несколькими отличиями:

  • Поля должны быть проинициализированы.

  • Поля считаются публичными статическими финальными.

  • Модификаторы public, static и final не нужно указывать явно (они «проставляются» по умолчанию).

И могут содержать default методы

Функциональные интерфейсы могут содержать дополнительно абстрактные методы, определенные в классе Object (вспоминаем третий модуль hashCode(); toString(); и т.д.).

  1. Основные семейства функциональных интерфейсов (они обобщённые <>)? (6 семейств)

  • Predicate Функциональный интерфейс Predicate проверяет соблюдение некоторого условия. Если оно соблюдается, то возвращается значение true. В качестве параметра лямбда-выражение принимает объект типа T:

public interface Predicate {boolean test(T t);}

  • BinaryOperator BinaryOperator принимает в качестве параметра два объекта типа T, выполняет над ними бинарную операцию и возвращает ее результат также в виде объекта типа T:

public interface BinaryOperator {T apply(T t1, T t2);}

  • UnaryOperator UnaryOperator принимает в качестве параметра объект типа T, выполняет над ними операции и возвращает результат операций в виде объекта типа T:

public interface UnaryOperator {T apply(T t);}

  • Function Функциональный интерфейс Function представляет функцию перехода от объекта типа T к объекту типа R:

public interface Function {R apply(T t);}

  • Consumer Consumer выполняет некоторое действие над объектом типа T, при этом ничего не возвращая:

public interface Consumer {void accept(T t);}

  • Supplier Supplier не принимает никаких аргументов, но должен возвращать объект типа T:

public interface Supplier { T get(); }


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

  • С помощью анонимного класса

  • С помощью лямбда - выражения.

  • С помощью ссылки на метод

  • С помощью обычного класса, путем имплементации


  1. Что такое метод референс?

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

Consumer consumer = System.out::println;

  1. Что такое «анонимные классы»? 

просто определение, и основное свойство - объявляется и создаётся одновременно.

Анонимный внутренний класс – это класс без имени, он должен использоваться, если вам необходимо переопределить методы класса или интерфейса.

  1. Чего НЕТ у анонимного класса?

Конструктора.

  1. Как создать экземпляр анонимного класса?

  • Если имя, следующее за ключевым словом new, это имя класса, то анонимный класс является подклассом этого класса.

  • Если имя, следующее за ключевым словом new, представляет собой интерфейс, то анонимный класс реализует этот интерфейс и расширяет класс Object.

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

  • Для анонимных классов, компилятор передаёт в конструктор скрытую ссылку .this на окружающий класс. Поэтому к объекту окружающего класса можно обращаться – через имя_внешнего_класса.this.

  • Анонимные классы, имеют доступ к локальным переменным своего блока кода которые объявлены как final или они должны быть effectively final.

  • Анонимные классы имеют доступ ко всем членам своего внешнего класса.

  • Если в анонимном классе объявлена переменная с таким же именем, как и в окружающем классе, то она затеняет переменную окружающего класса.

  • Анонимный класс не может содержать статические переменные, методы или классы, кроме констант static final. Интерфейс не может быть объявлен анонимно, потому что нет способа реализовать интерфейс без имени.

Анонимный внутренний класс может быть создан из класса или интерфейса. Синтаксис: { что-то };

Данный синтаксис не позволяет указать extends, implements или имя класса. В следствии этого анонимный класс может реализовать только один интерфейс.


  1. Что такое лямбда-выражение? Как его записать?

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


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

  • Лямбда-выражения – это «всего лишь» новый способ сделать то же самое, но в более чистом и менее многословном способе использования анонимных внутренних классов.

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

Интерфейс Comparable- используется для сравнения объектов, используя естественный порядок.

Единственный метод int compareTo(Element o).

Интерфейс Comparator -используется для сравнения объектов, использует не естественный порядок.

Единственный метод int compare (Element e1, Element e2)

  1. Что такое стримы? Для чего они нужны? Когда их лучше использовать?

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

  1. Какие есть виды стримов?

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

  • используют специализированные лямбда-выражения, например, IntFunction или IntPredicate вместо Function и Predicate;

  • поддерживают дополнительные конечные операции sum(), average(), mapToObj()

  1. Способы создания Стрима?

  • Из коллекции:

Stream fromCollection = Arrays.asList("x", "y", "z").stream();

  • Из набора значений:

Stream fromValues = Stream.of("x", "y", "z");

  • Из массива:

Stream fromArray = Arrays.stream(new String[]{"x", "y", "z"});

  • Из файла (каждая строка в файле будет отдельным элементом в стриме):

Stream fromFile = Files.lines(Paths.get("input.txt"));

  • Из строки:

IntStream fromString = "0123456789".chars();

  • С помощью Stream.builder():

Stream fromBuilder =Stream.builder().add("z").add("y").add("z").build();

  • С помощью Stream.iterate() (бесконечный):

Stream fromIterate = Stream.iterate(1, n -> n + 1);

  • С помощью Stream.generate() (бесконечный):

Stream fromGenerate = Stream.generate(() -> "0");

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

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

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

Операции над стримами бывают или промежуточными (intermediate) или конечными (terminal). Конечные операции возвращают результат определенного типа, а промежуточные операции возвращают тот же стрим. Таким образом вы можете строить цепочки из несколько операций над одним и тем же стримом.

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

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

Метод flatMap() похож на map, но может создавать из одного элемента несколько. Таким образом, каждый объект будет преобразован в ноль, один или несколько других объектов, поддерживаемых потоком. Наиболее очевидный способ применения этой операции — преобразование элементов контейнера при помощи функций, которые возвращают контейнеры.

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

Ленивая инициализация-это оптимизация производительности, при которой вы откладываете (потенциально дорогостоящее) создание объекта до тех пор, пока оно вам действительно не понадобится.

  1. Можно ли вызвать 2 терминальные операции?

нет

  1. Что будет, если терминальной операции не будет?

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

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

Нет, это одноразовая сущность

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

Если коротко - для ускорения. Но должны быть соблюдены некоторые условия, чтобы использование параллельных стримов было целесообразно:

  • Размер данных - чем больше данных, тем сложнее сначала разделять данные, а потом их соединять.

  • Количество ядер процессора. Теоретически, чем больше ядер в компьютере, тем быстрее программа будет работать. Если на машине одно ядро, нет смысла применять параллельные потоки.

  • Чем проще структура данных, с которой работает поток, тем быстрее будут происходить операции. Например, данные из ArrayList легко использовать, так как структура данной коллекции предполагает последовательность несвязанных данных. А вот коллекция типа LinkedList - не лучший вариант, так как в последовательном списке все элементы связаны с предыдущими/последующими. И такие данные трудно распараллелить.

  • Над данными примитивных типов операции будут производиться быстрее, чем над объектами классов.

  • Крайне не рекомендуется использовать параллельные стримы для долгих операций (например сетевых соединений), так как все параллельные стримы работают c одним ForkJoinPool, то такие долгие операции могут остановить работу всех параллельных стримов в JVM из-за отсутствия доступных потоков в пуле, т.е. параллельные стримы стоит использовать лишь для коротких операций, где счет идет на миллисекунды, но не для тех где счет может идти на секунды и минуты;

  • Сохранение порядка в параллельных стримах увеличивает издержки при выполнении и если порядок не важен, то имеется возможность отключить его сохранение и тем самым увеличить производительность, использовав промежуточную операцию unordered():

  1. В чём разница между forEach и peek?

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