Nowe funkcje w Javie 8

1. Przegląd

W tym artykule szybko przyjrzymy się niektórym z najciekawszych nowych funkcji języka Java 8.

Porozmawiamy o: domyślnych metodach interfejsu i metodach statycznych, odwołaniu do metod i opcjonalnych.

Omówiliśmy już niektóre funkcje wydania Java 8 - strumieniowe API, wyrażenia lambda i funkcjonalne interfejsy - ponieważ są to obszerne tematy, które zasługują na osobne spojrzenie.

2. Metody domyślne interfejsu i metody statyczne

Przed Java 8 interfejsy mogły mieć tylko publiczne metody abstrakcyjne. Nie było możliwe dodanie nowej funkcjonalności do istniejącego interfejsu bez wymuszania na wszystkich klasach implementujących utworzenia implementacji nowych metod, ani też nie było możliwe utworzenie metod interfejsu z implementacją.

Począwszy od języka Java 8, interfejsy mogą mieć statyczne i domyślne metody, które pomimo deklaracji w interfejsie mają zdefiniowane zachowanie.

2.1. Metoda statyczna

Rozważmy następującą metodę interfejsu (nazwijmy ten interfejs Vehicle ):

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

Statyczna metoda producenta () jest dostępna tylko za pośrednictwem interfejsu i wewnątrz niego. Nie może zostać przesłonięta przez klasę implementującą.

Aby wywołać go poza interfejsem, należy zastosować standardowe podejście do wywołania metody statycznej:

String producer = Vehicle.producer();

2.2. Metoda domyślna

Metody domyślne są deklarowane przy użyciu nowego domyślnego słowa kluczowego . Są one dostępne za pośrednictwem wystąpienia klasy implementującej i można je przesłonić.

Dodajmy domyślną metodę do naszego interfejsu Vehicle , która będzie również wywoływać statyczną metodę tego interfejsu:

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

Załóżmy, że ten interfejs jest zaimplementowany przez klasę VehicleImpl. Do wykonania metody domyślnej należy utworzyć instancję tej klasy:

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

3. Odniesienia do metod

Odwołanie do metody może służyć jako krótsza i bardziej czytelna alternatywa dla wyrażenia lambda, które wywołuje tylko istniejącą metodę. Istnieją cztery warianty odniesień do metod.

3.1. Odniesienie do metody statycznej

Odwołanie do metody statycznej ma następującą składnię: ContainingClass :: nazwaMetody.

Spróbujmy policzyć wszystkie puste ciągi na liście za pomocą Stream API.

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

Przyjrzeć się bliżej przy lambda wyrażenia w anyMatch () metody, to po prostu sprawia, że wywołanie metody statycznej isRealUser (podręcznik użytkownika) do użytkownika klasy. Można więc go zastąpić odwołaniem do metody statycznej:

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

Ten typ kodu wygląda na znacznie bardziej pouczający.

3.2. Odniesienie do metody wystąpienia

Odwołanie do metody instancji ma następującą składnię: c ontainingInstance :: nazwaMetody. Poniższy kod wywołuje metodę isLegalName (String string) typu User, która sprawdza poprawność parametru wejściowego:

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

3.3. Odniesienie do metody instancji obiektu określonego typu

Ta metoda referencyjna ma następującą składnię: C ontainingType :: nazwaMetody. Przykład::

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

3.4. Odniesienie do konstruktora

Odwołanie do konstruktora ma następującą składnię: ClassName :: new. Ponieważ konstruktor w Javie jest metodą specjalną, odwołanie do metody można zastosować do niej również za pomocą nazwy new jako nazwy metody .

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

4. Opcjonalnie

Przed Java 8 programiści musieli dokładnie sprawdzić wartości, do których się odwoływali, ze względu na możliwość wyrzucenia wyjątku NullPointerException (NPE) . Wszystkie te kontrole wymagały dość irytującego i podatnego na błędy kodu standardowego.

Klasa Java 8 Optional może pomóc w sytuacjach, w których istnieje możliwość uzyskania NPE . Działa jako kontener dla obiektu typu T. Może zwrócić wartość tego obiektu, jeśli ta wartość nie jest null . Gdy wartość w tym kontenerze jest zerowa , pozwala na wykonanie pewnych predefiniowanych akcji zamiast wyrzucania NPE.

4.1. Utworzenie opcjonalnego

Instancję klasy Optional można utworzyć za pomocą jej metod statycznych:

Optional optional = Optional.empty();

Zwraca puste pole opcjonalne.

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

Zwraca wartość opcjonalną, która zawiera wartość inną niż null.

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.

Wreszcie, cały kod źródłowy artykułu jest dostępny na GitHub.