Перейти к основному содержимому

Новые возможности в Java 8

· 6 мин. чтения

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.