Nuove funzionalità in Java 8

1. Panoramica

In questo articolo daremo una rapida occhiata ad alcune delle nuove funzionalità più interessanti di Java 8.

Parleremo di: interfaccia predefinita e metodi statici, riferimento al metodo e Opzionale.

Abbiamo già coperto alcune delle funzionalità della versione di Java 8 - API di flusso, espressioni lambda e interfacce funzionali - poiché sono argomenti completi che meritano uno sguardo separato.

2. Metodi predefiniti e statici dell'interfaccia

Prima di Java 8, le interfacce potevano avere solo metodi astratti pubblici. Non è stato possibile aggiungere nuove funzionalità all'interfaccia esistente senza forzare tutte le classi di implementazione a creare un'implementazione dei nuovi metodi, né è stato possibile creare metodi di interfaccia con un'implementazione.

A partire da Java 8, le interfacce possono avere metodi statici e predefiniti che, nonostante siano dichiarati in un'interfaccia, hanno un comportamento definito.

2.1. Metodo statico

Considera il seguente metodo dell'interfaccia (chiamiamo questa interfaccia Veicolo ):

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

Il metodo statico producer () è disponibile solo attraverso e all'interno di un'interfaccia. Non può essere sovrascritto da una classe di implementazione.

Per chiamarlo al di fuori dell'interfaccia dovrebbe essere utilizzato l'approccio standard per la chiamata al metodo statico:

String producer = Vehicle.producer();

2.2. Metodo predefinito

I metodi predefiniti vengono dichiarati utilizzando la nuova parola chiave predefinita . Questi sono accessibili tramite l'istanza della classe di implementazione e possono essere sovrascritti.

Aggiungiamo un metodo predefinito alla nostra interfaccia del veicolo , che effettuerà anche una chiamata al metodo statico di questa interfaccia:

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

Supponiamo che questa interfaccia sia implementata dalla classe VehicleImpl. Per eseguire il metodo predefinito è necessario creare un'istanza di questa classe:

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

3. Riferimenti al metodo

Il riferimento al metodo può essere usato come alternativa più breve e più leggibile per un'espressione lambda che chiama solo un metodo esistente. Sono disponibili quattro varianti di riferimenti al metodo.

3.1. Riferimento a un metodo statico

Il riferimento a un metodo statico contiene la seguente sintassi: ContainingClass :: methodName.

Proviamo a contare tutte le stringhe vuote nell'elenco con l'aiuto dell'API Stream.

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

Dai un'occhiata più da vicino all'espressione lambda nel metodo anyMatch () , effettua solo una chiamata a un metodo statico isRealUser (User user) della classe User . Quindi può essere sostituito con un riferimento a un metodo statico:

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

Questo tipo di codice sembra molto più informativo.

3.2. Riferimento a un metodo di istanza

Il riferimento a un metodo di istanza contiene la seguente sintassi: c ontainInstance :: methodName. Il seguente codice chiama il metodo isLegalName (String string) di tipo User che convalida un parametro di input:

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

3.3. Riferimento a un metodo di istanza di un oggetto di un tipo particolare

Questo metodo di riferimento accetta la seguente sintassi: C ontainType :: methodName. Un esempio::

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

3.4. Riferimento a un costruttore

Un riferimento a un costruttore accetta la seguente sintassi: ClassName :: new. Poiché il costruttore in Java è un metodo speciale, il riferimento al metodo potrebbe essere applicato anche ad esso con l'aiuto di new come nome del metodo .

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

4. Facoltativo

Prima che gli sviluppatori Java 8 dovessero convalidare attentamente i valori a cui si riferivano, a causa della possibilità di lanciare NullPointerException (NPE) . Tutti questi controlli richiedevano un codice boilerplate piuttosto fastidioso e soggetto a errori.

La classe Java 8 opzionale può aiutare a gestire le situazioni in cui esiste la possibilità di ottenere l' NPE . Funziona come un contenitore per l'oggetto di tipo T. Può restituire un valore di questo oggetto se questo valore non è nullo . Quando il valore all'interno di questo contenitore è nullo , consente di eseguire alcune azioni predefinite invece di lanciare NPE.

4.1. Creazione dell'Opzionale

Un'istanza della classe Optional può essere creata con l'aiuto dei suoi metodi statici:

Optional optional = Optional.empty();

Restituisce un Opzionale vuoto .

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

Restituisce un optional che contiene un valore non nullo.

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.

Infine, tutto il codice sorgente dell'articolo è disponibile su GitHub.