- Новое в Java 8
- Методы интерфейсов по умолчанию
- Лямбда-выражения
- Функциональные интерфейсы
- Ссылки на методы и конструкторы
- Области действия лямбд
- Встроенные функциональные интерфейсы
- Потоки
- Параллельные потоки
- Ассоциативные массивы
- API для работы с датами
- Аннотации
- Вот и все
- Функциональные интерфейсы Java
- Реализация с помощью лямбда-выражения
- Встроенные функциональные интерфейсы
- Function
- Predicate
- UnaryOperator
- BinaryOperator
- Supplier
- Consumer
Новое в Java 8
Методы интерфейсов по умолчанию
Java 8 позволяет вам добавлять неабстрактные реализации методов в интерфейс, используя ключевое слово default . Эта фича также известна, как методы расширения. Вот наш первый пример:
Кроме абстрактного метода calculate интерфейс Formula также определяет метод по умолчанию sqrt . Классы, имплементирующие этот интерфейс, должны переопределить только абстрактный метод calculate . Метод по умолчанию sqrt будет доступен без переопределения.
formula реализован как анонимный объект. Этот код довольно избыточен: целых 6 строчек для такого простого вычисления как sqrt(a * 100) . В следующем разделе мы увидим, что в Java 8 есть куда более изящный способ реализации объектов с одним методом.
Лямбда-выражения
Давайте начнем с простого примера: сортировка массива строк в предыдущих версиях языка.
Статический метод Collections.sort принимает список и компаратор, который используется для сортировки списка. Наверняка вам часто приходилось создавать анонимные компараторы для того чтобы передать их в метод.
Java 8 предоставляет гораздо более короткий синтаксис — лямбда-выражения, чтобы вам не приходилось тратить время на создание анонимных объектов:
Как видите, код гораздо короче и куда более читаем. И его можно сделать еще короче:
Для однострочных методов вы можете опустить скобки <> и ключевое слово return . Так еще короче:
Компилятору известны типы параметров, поэтому их можно тоже опустить. Давайте посмотрим, как еще могут использовать лямбда-выражения.
Функциональные интерфейсы
Как лямбда-выражения соответствуют системе типов языка Java? Каждой лямбде соответствует тип, представленный интерфейсом. Так называемый функциональный интерфейс должен содержать ровно один абстрактный метод. Каждое лямбда-выражение этого типа будет сопоставлено объявленному методу. Также, поскольку методы по умолчанию не являются абстрактными, вы можете добавлять в функциональный интерфейс сколько угодно таких методов.
Мы можем использовать какие угодно интерфейсы для лямбда-выражений, содержащие ровно один абстрактный метод. Для того, чтобы гарантировать, что ваш интерфейс отвечает этому требованию, используется аннотация @FunctionalInterface . Компилятор осведомлен об этой аннотации, и выдаст ошибку компиляции, если вы добавите второй абстрактный метод в функциональный интерфейс.
Имейте в виду, что этот код останется корректным даже если убрать аннотацию @FunctionalInterface .
Ссылки на методы и конструкторы
Предыдущий пример можно упростить, если использовать статические ссылки на методы:
Java 8 позволяет вам передавать ссылки на методы или конструкторы. Для этого нужно использовать ключевое слово :: . Предыдущий пример иллюстрирует передачу ссылки на статический метод. Однако мы также можем ссылаться на экземплярный метод:
Давайте посмотрим, как передавать ссылки на конструкторы. Сперва определим бин с несколькими конструкторами:
Затем определим интерфейс фабрики, которая будет использоваться для создания новых персон:
Теперь вместо реализации интерфейса мы соединяем все вместе при помощи ссылки на конструктор:
Мы создаем ссылку на конструктор с помощью Person::new . Компилятор автоматически выбирает подходящий конструктор, сигнатура которого совпадает с сигнатурой PersonFactory.create .
Области действия лямбд
Доступ к переменным внешней области действия из лямбда-выражения очень схож к доступу из анонимных объектов. Вы можете ссылаться на переменные, объявленные как final , на экземплярные поля класса и статические переменные.
Но в отличии от анонимных объектов, переменная num не обязательно должна быть объявлена как final . Такой код тоже сработает:
Однако переменная num должна все равно оставаться неизменяемой. Следующий код не скомпилируется:
Запись в переменную num в пределах лямбда-выражения также запрещена.
Доступ к полям и статическим переменным
В отличии от локальных переменных, мы можем записывать значения в экземплярные поля класса и статические переменные внутри лямбда-выражений. Это поведение хорошо знакомо по анонимным объектам.
Доступ к методам интерфейсов по умолчанию
Помните пример с формулой из первого раздела? В интерфейсе Formula определен метод по умолчанию sqrt , который доступен из каждой имплементации интерфейса, включая анонимные объекты. Однако это не сработает в лямбда-выражениях.
Внутри лямбда-выражений запрещено обращаться к методам по умолчанию. Следующий код не скомпилируется:
Встроенные функциональные интерфейсы
JDK 1.8 содержит множество встроенных функциональных интерфейсов. Некоторые из них хорошо известны по предыдущим версиям языка, например, Comparator или Runnable . Все эти интерфейсы были поддержаны в лямбдах добавлением аннотации @FunctionalInterface .
Однако в Java 8 также появилось много новых функциональных интерфейсов, которые призваны облегчить вам жизнь. Некоторые интерфейсы хорошо известны по библиотеке Google Guava. Даже если вы незнакомы с этой библиотекой, вам стоить взглянуть, как эти интерфейсы были дополнены некоторыми полезными методами расширений.
Предикаты
Предикаты — это функции, принимающие один аргумент, и возвращающие значение типа boolean. Интерфейс содержит различные методы по умолчанию, позволяющие строить сложные условия ( and , or , negate ).
Функции
Функции принимают один аргумент и возвращают некоторый результат. Методы по умолчанию могут использоваться для построения цепочек вызовов ( compose , andThen ).
Поставщики
Поставщики (suppliers) предоставляют результат заданного типа. В отличии от функций, поставщики не принимают аргументов.
Потребители
Потребители (consumers) представляют собой операции, которые производятся на одним входным аргументом.
Компараторы
Компараторы хорошо известны по предыдущим версиям Java. Java 8 добавляет в интерфейс различные методы по умолчанию.
Опциональные значения
Опциональные значения (optionals) не являются функциональными интерфейсами, однако являются удобным средством предотвращения NullPointerException. Это важная концепция, которая понадобится нам в следующем разделе, поэтому давайте взглянем, как работают опциональные значения.
Опциональные значение — это по сути контейнер для значения, которое может быть равно null. Например, вам нужен метод, который возвращает какое-то значение, но иногда он должен возвращать пустое значение. Вместо того, чтобы возвращать null, в Java 8 вы можете вернуть опциональное значение.
Потоки
Тип java.util.Stream представляет собой последовательность элементов, над которой можно производить различные операции. Операции над потоками бывают или промежуточными (intermediate) или конечными (terminal). Конечные операции возвращают результат определенного типа, а промежуточные операции возвращают тот же поток. Таким образом вы можете строить цепочки из несколько операций над одним и тем же потоком. Поток создаются на основе источников, например типов, реализующих java.util.Collection , такие как списки или множества (ассоциативные массивы не поддерживаются). Операции над потоками могут выполняться как последовательно, так и параллельно.
Сначала давайте посмотрим, как работать с потоком последовательно. Сперва создадим источник в виде списка строк:
В Java 8 вы можете быстро создавать потоки, используя вызовы Collection.stream() или Collection.parallelStream() . Следующие разделы объясняют наиболее распространенные операции над потоками.
Filter
Операция Filter принимает предикат, который фильтрует все элементы потока. Эта операция является промежуточной, т.е. позволяет нам вызвать другую операцию (например, forEach ) над результатом. ForEach принимает функцию, которая вызывается для каждого элемента в (уже отфильтрованном) поток. ForEach является конечной операцией. Она не возращает никакого значения, поэтому дальнейший вызов потоковых операций невозможен.
Sorted
Операция Sorted является промежуточной операцией, которая возвращает отсортированное представление потока. Элементы сортируются в обычном порядке, если вы не предоставили свой компаратор:
Помните, что sorted создает всего лишь отсортированное представление и не влияет на порядок элементов в исходной коллекции. Порядок строк в stringCollection остается нетронутым:
Промежуточная операция map преобразовывает каждый элемент в другой объект при помощи переданной функции. Следующий пример преобразовывает каждую строку в строку в верхнем регистре. Однако вы так же можете использовать map для преобразования каждого объекта в объект другого типа. Тип результирующего потока зависит от типа функции, которую вы передаете при вызове map .
Match
Для проверки, удовлетворяет ли поток заданному предикату, используются различные операции сопоставления (match). Все операции сопоставления являются конечными и возвращают результат типа boolean.
Count
Операция Count является конечной операцией и возвращает количество элементов в потоке. Типом возвращаемого значения является long .
Reduce
Эта конечная операция производит свертку элементов потока по заданной функции. Результатом является опциональное значение.
Параллельные потоки
Как уже упоминалось выше, потоки могут быть последовательными и параллельными. Операции над последовательными потоками выполняются в одном потоке процессора, над параллельными — используя несколько потоков процессора.
Следующие пример демонстрирует, как можно легко увеличить скорость работы, используя параллельные потоки.
Сперва создадим большой список из уникальных элементов:
Теперь измерим время сортировки этого списка.
Последовательная сортировка
Параллельная сортировка
Как вы можете видеть, оба куска кода практически идентичны, однако параллельная сортировка почти в два раза быстрее. Все, что вам нужно сделать, это заменить вызов stream() на parallelStream() .
Ассоциативные массивы
Как уже упоминалось, ассоциативные массивы (maps) не поддерживают потоки. Вместо этого ассоциативные массивы теперь поддерживают различные полезные методы, которые решают часто встречаемые задачи.
Этот код в особых комментариях не нуждается: putIfAbsent позволяет нам не писать дополнительные проверки на null; forEach принимает потребителя, который производит операцию над каждым элементом массива.
Этот код показывает как использовать для вычислений код при помощи различных функций:
Затем мы узнаем, как удалить объект по ключу, только если этот объект ассоциирован с ключом:
Еще один полезный метод:
Объединить записи двух массивов? Легко:
В случае отсутствия ключа Merge создает новую пару ключ-значение. В противном случае — вызывает функцию объединения для существующего значения.
API для работы с датами
Java 8 содержит совершенно новый API для работы с датами и временем, расположенный в пакете java.time . Новый API сравним с библиотекой Joda-Time, однако имеются отличия. Следующие разделы рассказывают о наиболее важных частях нового API.
Clock
Тип Clock предоставляет доступ к текущей дате и времени. Этот тип знает о часовых поясах и может использоваться вместо вызова System.currentTimeMillis() для возвращения миллисекунд. Такая точная дата также может быть представлена классом Instant . Объекты этого класса могут быть использованы для создания объектов устаревшего типа
java.util.Date .
Часовые пояса
Часовые пояса представлены типом ZoneId . Доступ к ним можно получить при помощи статических фабричных методов. Часовые пояса содержат смещения, которые важны для конвертации дат и времени в местные.
LocalTime
Тип LocalTime представляет собой время с учетом часового пояса, например, 10pm или 17:30:15. В следующем примере создаются два местных времени для часовых поясов, определенных выше. Затем оба времени сравниваются, и вычисляется разница между ними в часах и минутах.
Тип LocalTime содержит различные фабричные методы, которые упрощают создание новых экземпляров, а также парсинг строк.
LocalDate
Тип LocalDate представляет конкретную дату, например, 2014-03-11. Объекты LocalDate неизменяемы и являются аналогом LocalTime . Пример демонстрирует вычисление новой даты путем сложения или вычитания дней, месяцев или годов. Помните, что каждая операция возвращает новый экземпляр.
Создание экземпляра LocalDate путем парсинга строки:
LocalDateTime
Тип LocalDateTime представляет собой комбинацию даты и времени. Объекты LocalDateTime неизменяемы и работают аналогично LocalTime и LocalDate . Мы можем использовать различные методы для извлечения конкретных значений из даты-времени:
Путем добавления информации о часовом поясе мы можем получить Instant .
Форматирование даты-времени работает так же, как и форматирование даты или времени. Мы можем использовать библиотечные или свои собственные шаблоны.
В отличии от java.text.NumberFormat , новый DateTimeFormatter является неизменяемым и потокобезопасным.
Подробно о синтаксисе шаблонов можно почитать здесь.
Аннотации
Аннотации в Java 8 являются повторяемыми. Давайте сразу посмотрим пример, чтобы понять, что это такое.
Сперва мы определим аннотацию-обертку, которая содержит массив аннотаций:
Java 8 позволяет нам использовать множество аннотаций одного типа путем указания аннотации @Repeatable .
Вариант 1: использовать аннотацию-контейнер (старый способ)
Вариант 2: использовать повторяемую аннотацию (новый способ)
При использовании варианта 2 компилятор автоматически подставляет аннотацию @Hints . Это важно при чтении информации об аннотациях через рефлексию.
Хотя мы никогда не объявляли аннотацию @Hints в классе Person , она доступна нам при вызове getAnnotation(Hints.class) . Однако более удобным является метод getAnnotationsByType , который напрямую предоставляет доступ ко всем аннотациям @Hint .
Более того, аннотации в Java 8 можно использовать еще на двух элементах:
Вот и все
На этом введение в программирование на Java 8 завершено. Вам остается самостоятельно изучить другие новинки JDK 1.8, например, Arrays.parallelSort , StampedLock , CompletableFuture и другие.
Полный исходный код статьи доступен на GitHub.
Источник
Функциональные интерфейсы Java
Термин функциональный интерфейс был введен в Java 8. Это интерфейс, который содержит только один абстрактный (не реализованный) метод. Может содержать стандартные и статические, которые имеют реализацию, в дополнение к одному нереализованному.
Вышеуказанное содержит только один метод, и этот метод не имеет реализации.
Обычно интерфейс не содержит реализации методов, которые он объявляет, но он может содержать реализации в методах по умолчанию или в статических. Ниже приведен еще один пример с реализациями некоторых методов:
Вышеупомянутый интерфейс все еще считается функциональным, поскольку он содержит только один не реализованный метод.
Реализация с помощью лямбда-выражения
Лямбда-выражение реализует единственный метод из интерфейса. Чтобы узнать, какой метод реализует лямбда-выражение, интерфейс может содержать только один не реализованный метод. Другими словами, он должен быть функциональным.
Встроенные функциональные интерфейсы
Есть разработанные виды для часто встречающихся вариантов использования, поэтому вам не нужно создавать свои собственные функциональные интерфейсы для каждого небольшого варианта использования.
Function
Интерфейс Function interface(java.util.function.Function) является одним из самых центральных функциональных интерфейсов. Представляет функцию (метод), которая принимает один параметр и возвращает одно значение. Вот как выглядит определение:
Интерфейс Function на самом деле содержит несколько дополнительных методов в дополнение к методам, перечисленным выше, но, поскольку все они поставляются с реализацией по умолчанию, вам не нужно реализовывать их.
Единственный метод, который необходимо реализовать для реализации интерфейса Function, – это apply(). Вот пример реализации функции:
В этой реализации функции реализован метод apply(), поэтому он принимает параметр Long в качестве параметра и возвращает Long. Вот пример использования вышеупомянутого класса AddThree:
- Этот пример создает новый экземпляр AddThree и назначает его переменной Function.
- Вызывается метод apply() для экземпляра AddThree.
- Распечатывает результат (который равен 7).
Вы также можете реализовать Function с помощью лямбда-выражения:
Как видите, реализация Function теперь встроена в объявление переменной adderLambda, а не в отдельный класс. Это немного короче, плюс мы можем видеть непосредственно в приведенном выше коде, что он делает.
Predicate
Интерфейс Java Predicate, java.util.function.Predicate, представляет простую функцию, которая принимает одно значение в качестве параметра и возвращает true или false:
Интерфейс Predicate содержит больше методов, чем метод test(), но остальные являются стандартными или статическими, которые вам не нужно реализовывать.
Вы можете реализовать Predicate, используя класс, например так:
Вы также можете реализовать Predicate, используя лямбда-выражение:
Эта лямбда-реализация Predicate фактически делает то же самое, что и реализация выше, использующая класс.
UnaryOperator
Интерфейс Java UnaryOperator представляет операцию, которая принимает один параметр и возвращает параметр того же типа:
Интерфейс UnaryOperator может использоваться для представления операции, которая принимает конкретный объект в качестве параметра, изменяет этот объект и возвращает его снова – возможно, как часть цепочки обработки функционального потока.
BinaryOperator
BinaryOperator – это функциональный интерфейс, представляющий операцию, которая принимает два параметра и возвращает одно значение. Оба параметра и тип возвращаемого значения должны быть одного типа. Полезен при реализации функций, которые суммируют, вычитают, делят, умножают и т. д. Два элемента одного типа и возвращают третий элемент того же типа.
Supplier
Интерфейс Supplier – это функциональный интерфейс, представляющий функцию, которая предоставляет значение некоторых видов. Также можно рассматривать как фабричный интерфейс:
Эта реализация Java Supplier возвращает новый экземпляр Integer со случайным значением от 0 до 1000.
Consumer
Consumer – это функциональный интерфейс, представляющий функцию, которая потребляет значение без возврата какого-либо значения. Реализация может распечатывать значение или записывать его в файл, или по сети и т. д. Реализация:
Источник