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

1. Обзор

В этой статье мы кратко рассмотрим некоторые из наиболее интересных новых функций Java 8.

Мы поговорим о: интерфейсах по умолчанию и статических методах, справочниках по методам и Optional.

Мы уже рассмотрели некоторые особенности выпуска Java 8 - потоковый API, лямбда-выражения и функциональные интерфейсы - поскольку они всеобъемлющие темы, заслуживающие отдельного рассмотрения.

2. Стандартные и статические методы интерфейса

До Java 8 интерфейсы могли иметь только общедоступные абстрактные методы. Невозможно было добавить новую функциональность к существующему интерфейсу, не заставив все реализующие классы создать реализацию новых методов, а также невозможно было создать методы интерфейса с помощью реализации.

Начиная с Java 8, интерфейсы могут иметь статические методы и методы по умолчанию, которые, несмотря на то, что объявлены в интерфейсе, имеют определенное поведение.

2.1. Статический метод

Рассмотрим следующий метод интерфейса (назовем этот интерфейс Vehicle ):

static String producer() { return "N&F Vehicles"; }

Статический метод производителя () доступен только через интерфейс и внутри него. Он не может быть отменен классом реализации.

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

String producer = Vehicle.producer();

2.2. Метод по умолчанию

Методы по умолчанию объявляются с использованием нового ключевого слова default . Они доступны через экземпляр реализующего класса и могут быть переопределены.

Давайте добавим метод по умолчанию в наш интерфейс Vehicle , который также будет вызывать статический метод этого интерфейса:

default String getOverview() { return "ATV made by " + producer(); }

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

Vehicle vehicle = new VehicleImpl(); String overview = vehicle.getOverview();

3. Ссылки на методы

Ссылку на метод можно использовать как более короткую и удобочитаемую альтернативу лямбда-выражению, которое вызывает только существующий метод. Есть четыре варианта ссылок на методы.

3.1. Ссылка на статический метод

Ссылка на статический метод имеет следующий синтаксис: ContainClass :: methodName.

Попробуем подсчитать все пустые строки в списке с помощью Stream API.

boolean isReal = list.stream().anyMatch(u -> User.isRealUser(u));

Присмотритесь на лямбда - выражения в AnyMatch () метод, он просто делает вызов метода статического isRealUser (пользователя) пользователя из пользовательского класса. Таким образом, его можно заменить ссылкой на статический метод:

boolean isReal = list.stream().anyMatch(User::isRealUser);

Такой код выглядит намного информативнее.

3.2. Ссылка на метод экземпляра

Ссылка на метод экземпляра имеет следующий синтаксис: c ontainInstance :: methodName. Следующий код вызывает метод isLegalName (String string) типа User, который проверяет входной параметр:

User user = new User(); boolean isLegalName = list.stream().anyMatch(user::isLegalName); 

3.3. Ссылка на метод экземпляра объекта определенного типа

Этот ссылочный метод имеет следующий синтаксис: C ontainType :: methodName. Пример::

long count = list.stream().filter(String::isEmpty).count();

3.4. Ссылка на конструктор

Ссылка на конструктор имеет следующий синтаксис: ClassName :: new. Поскольку конструктор в Java - это особый метод, ссылка на метод также может быть применена к нему с помощью new в качестве имени метода .

Stream stream = list.stream().map(User::new);

4. Необязательно

До появления Java 8 разработчикам приходилось тщательно проверять значения, на которые они ссылались, из-за возможности выброса исключения NullPointerException (NPE) . Все эти проверки требовали довольно надоедливого и подверженного ошибкам шаблонного кода.

Необязательный класс Java 8 может помочь справиться с ситуациями, когда есть возможность получить NPE . Он работает как контейнер для объекта типа T. Он может возвращать значение этого объекта, если это значение не является нулем . Когда значение внутри этого контейнера равно нулю, это позволяет выполнять некоторые предопределенные действия вместо вызова NPE.

4.1. Создание необязательного

Экземпляр необязательного класса можно создать с помощью его статических методов:

Optional optional = Optional.empty();

Возвращает пустой Необязательный.

String str = "value"; Optional optional = Optional.of(str);

Возвращает Optional, который содержит ненулевое значение.

Optional optional = Optional.ofNullable(getString());

Will return an Optional with a specific value or an empty Optional if the parameter is null.

4.2. Optional Usage

For example, you expect to get a List and in the case of null you want to substitute it with a new instance of an ArrayList. With pre-Java 8's code you need to do something like this:

List list = getList(); List listOpt = list != null ? list : new ArrayList();

With Java 8 the same functionality can be achieved with a much shorter code:

List listOpt = getList().orElseGet(() -> new ArrayList());

There is even more boilerplate code when you need to reach some object's field in the old way. Assume you have an object of type User which has a field of type Address with a field street of type String. And for some reason you need to return a value of the street field if some exist or a default value if street is 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";

This can be simplified with Optional:

Optional user = Optional.ofNullable(getUser()); String result = user .map(User::getAddress) .map(Address::getStreet) .orElse("not specified");

In this example we used the map() method to convert results of calling the getAdress() to the Optional and getStreet() to Optional. If any of these methods returned null the map() method would return an empty Optional.

Imagine that our getters return Optional. So, we should use the flatMap() method instead of the map():

Optional optionalUser = Optional.ofNullable(getOptionalUser()); String result = optionalUser .flatMap(OptionalUser::getAddress) .flatMap(OptionalAddress::getStreet) .orElse("not specified");

Another use case of Optional is changing NPE with another exception. So, as we did previously, let's try to do this in pre-Java 8's style:

String value = null; String result = ""; try { result = value.toUpperCase(); } catch (NullPointerException exception) { throw new CustomException(); }

And what if we use Optional? The answer is more readable and simpler:

String value = null; Optional valueOpt = Optional.ofNullable(value); String result = valueOpt.orElseThrow(CustomException::new).toUpperCase();

Notice, that how and for what purpose to use Optional in your app is a serious and controversial design decision, and explanation of its all pros and cons is out of the scope of this article. If you are interested, you can dig deeper, there are plenty of interesting articles on the Internet devoted to this problem. This one and this another one could be very helpful.

5. Conclusion

In this article, we are briefly discussing some interesting new features in Java 8.

There are of course many other additions and improvements which are spread across many Java 8 JDK packages and classes.

But, the information illustrated in this article is a good starting point for exploring and learning about some of these new features.

Наконец, весь исходный код статьи доступен на GitHub.