1. Обзор
В этом руководстве мы кратко рассмотрим некоторые из наиболее интересных новых функций Java 8.
Мы поговорим об интерфейсе по умолчанию и статических методах, справочнике по методам и необязательных.
Мы уже рассмотрели некоторые возможности версии Java 8 — потоковый API , лямбда-выражения и функциональные интерфейсы — поскольку это обширные темы, заслуживающие отдельного рассмотрения.
2. Интерфейс по умолчанию и статические методы
До Java 8 интерфейсы могли иметь только общедоступные абстрактные методы. Было невозможно добавить новую функциональность к существующему интерфейсу, не заставляя все реализующие классы создавать реализацию новых методов, а также невозможно было создать методы интерфейса с реализацией.
Начиная с Java 8, интерфейсы могут иметь статические
методы и методы по умолчанию
, которые, несмотря на то, что они объявлены в интерфейсе, имеют определенное поведение.
2.1. Статический метод
Рассмотрим этот метод интерфейса (назовем этот интерфейс Vehicle
):
static String producer() {
return "N&F Vehicles";
}
Статический метод производителя ()
доступен только через и внутри интерфейса. Он не может быть переопределен реализующим классом.
Для вызова вне интерфейса следует использовать стандартный подход для вызова статического метода:
String producer = Vehicle.producer();
2.2. Метод по умолчанию
Методы по умолчанию объявляются с использованием нового ключевого слова по умолчанию .
Они доступны через экземпляр реализующего класса и могут быть переопределены.
Давайте добавим в наш интерфейс Vehicle метод по
умолчанию
, который также будет вызывать статический
метод этого интерфейса:
default String getOverview() {
return "ATV made by " + producer();
}
Предположим, что этот интерфейс реализован классом VehicleImpl
.
Для выполнения метода по умолчанию
необходимо создать экземпляр этого класса:
Vehicle vehicle = new VehicleImpl();
String overview = vehicle.getOverview();
3. Ссылки на методы
Ссылку на метод можно использовать как более короткую и удобочитаемую альтернативу лямбда-выражению, которое вызывает только существующий метод. Существует четыре варианта ссылок на методы.
3.1. Ссылка на статический метод
Ссылка на статический метод содержит синтаксис ContainingClass::methodName
.
Попробуем подсчитать все пустые строки в List<String>
с помощью Stream API:
boolean isReal = list.stream().anyMatch(u -> User.isRealUser(u));
Давайте подробнее рассмотрим лямбда-выражение в методе anyMatch()
. Он просто вызывает статический метод isRealUser(User user)
класса User
.
Таким образом, его можно заменить ссылкой на статический метод:
boolean isReal = list.stream().anyMatch(User::isRealUser);
Этот тип кода выглядит намного информативнее.
3.2. Ссылка на метод экземпляра
Ссылка на метод экземпляра содержит синтаксис , содержащийInstance::methodName
. ``
Следующий код вызывает метод isLegalName(String string)
типа User
, который проверяет входной параметр:
User user = new User();
boolean isLegalName = list.stream().anyMatch(user::isLegalName);
3.3. Ссылка на метод экземпляра объекта определенного типа
Этот ссылочный метод использует синтаксис ContainingType::methodName
. ``
Давайте посмотрим на пример:
long count = list.stream().filter(String::isEmpty).count();
3.4. Ссылка на конструктор
Ссылка на конструктор имеет синтаксис ClassName::new
. ``
Поскольку конструктор в Java — это особый метод, к нему тоже можно применить ссылку на метод с помощью new
в качестве имени метода:
Stream<User> stream = list.stream().map(User::new);
4. Необязательно<T>
До Java 8 разработчикам приходилось тщательно проверять значения, на которые они ссылались, из-за возможности создания исключения NullPointerException (NPE)
. Все эти проверки требовали довольно раздражающего и подверженного ошибкам шаблонного кода.
Класс Optional<T>
Java 8 может помочь справиться с ситуациями, когда существует вероятность получения NPE
. Он работает как контейнер для объекта типа T
. Он может вернуть значение этого объекта, если это значение не равно null
. Когда значение внутри этого контейнера равно null
, это позволяет выполнять некоторые предопределенные действия вместо создания NPE
.
4.1. Создание необязательного <T>
Экземпляр класса Optional
может быть создан с помощью его статических методов.
Давайте посмотрим, как вернуть пустой option
:
Optional<String> optional = Optional.empty();
Затем мы возвращаем необязательный
элемент, содержащий ненулевое значение:
String str = "value";
Optional<String> optional = Optional.of(str);
Наконец, вот как вернуть необязательный
параметр с определенным значением или пустой необязательный
параметр, если параметр имеет значение null
:
Optional<String> optional = Optional.ofNullable(getString());
4.2. Необязательное использование <T>
Допустим, мы ожидаем получить List<String>
, и в случае null
мы хотим заменить его новым экземпляром ArrayList<String>
.
С кодом до Java 8 нам нужно сделать что-то вроде этого:
List<String> list = getList();
List<String> listOpt = list != null ? list : new ArrayList<>();
В Java 8 та же функциональность может быть достигнута с помощью гораздо более короткого кода:
List<String> listOpt = getList().orElseGet(() -> new ArrayList<>());
Существует еще больше шаблонного кода, когда нам нужно добраться до какого-то поля объекта по-старому.
Предположим, у нас есть объект типа User
, у которого есть поле типа Address
с полем street
типа String
, и нам нужно вернуть значение поля street
, если оно существует, или значение по умолчанию, если street
равно null
:
User user = getUser();
if (user != null) {
Address address = user.getAddress();
if (address != null) {
String street = address.getStreet();
if (street != null) {
return street;
}
}
}
return "not specified";
Это можно упростить с помощью опционального
:
Optional<User> user = Optional.ofNullable(getUser());
String result = user
.map(User::getAddress)
.map(Address::getStreet)
.orElse("not specified");
В этом примере мы использовали метод map()
для преобразования результатов вызова getAdress()
в Optional<Address>
и getStreet()
в Optional<String>
. Если бы какой-либо из этих методов вернул null
, метод map()
вернул бы пустое значениеOptional
.
Теперь представьте, что наши геттеры возвращают Optional<T>
.
В этом случае мы должны использовать метод flatMap()
вместо map()
:
Optional<OptionalUser> optionalUser = Optional.ofNullable(getOptionalUser());
String result = optionalUser
.flatMap(OptionalUser::getAddress)
.flatMap(OptionalAddress::getStreet)
.orElse("not specified");
Еще один вариант использования Optional
— изменение NPE
с другим исключением.
Итак, как мы делали ранее, давайте попробуем сделать это в стиле до Java 8:
String value = null;
String result = "";
try {
result = value.toUpperCase();
} catch (NullPointerException exception) {
throw new CustomException();
}
И ответ будет более читабельным и простым, если мы используем Optional<String>
:
String value = null;
Optional<String> valueOpt = Optional.ofNullable(value);
String result = valueOpt.orElseThrow(CustomException::new).toUpperCase();
Обратите внимание, как и для чего использовать Optional
в нашем приложении — это серьезное и спорное дизайнерское решение, и объяснение всех его плюсов и минусов выходит за рамки этой статьи. Но есть много интересных статей, посвященных этой проблеме. Этот и этот могут быть очень полезными, чтобы копнуть глубже.
5. Вывод
В этой статье мы кратко обсудили некоторые интересные новые функции в Java 8.
Конечно, во многих пакетах и классах Java 8 JDK есть много других дополнений и улучшений.
Но информация, проиллюстрированная в этой статье, является хорошей отправной точкой для изучения некоторых из этих новых функций.
Наконец, весь исходный код статьи доступен на GitHub.