Slide quinta lezione al linguaggio Java 8 in preparazione alla certificazione OCA 1Z0-808.
Argomenti:
Invocazione virtuale dei metodi
Eccezioni, gestione e creazione
Catturare e rilanciare eccezioni
NullPointer
Operazioni lambda, Predicate e principali operazioni
Stream
Lezione del 05-12-2017 tenuta da Valerio Radice presso Nextre Engeneering
https://www.nextre.it/corso/corso-java-oca/
3. Di cosa si parlerà ?
Invocazione virtuale dei metodi
Eccezioni, gestione e creazione
Catturare e rilanciare eccezioni
NullPointer
Operazioni lambda, Predicate e principali operazioni
Stream
4. Invocazione virtuale dei metodi
Un'invocazione a un metodo m può definirsi virtuale quando m è definito in una classe
A, ridefinito nella sottoclasse B(override) e invocato su un'instanza di B tramite una
reference di A(polimorfismo per dati). Il compilatore pensa di avviare il metodo m di
A(virtualmente) ma in realtà viene invocato il metodo m della classe B!
Quello che avviene è che il metodo m di B ha sovrascritto lo stesso intervallo di
puntamento che usa A
Giocatore g4 = new GiocatoreArcade("Pong");
System.out.println(g4.toString());
Giocatore Creato
GiocatoreArcade{punteggio=0, nominativo='Bipede da sala giochi', id=12012, gioco='Pong'}
5. Eccezione
Situazione imprevista che può presentarsi durante il flusso di un'applicazione.
Alcune eccezioni possono essere catturate per gestire una risposta del sistema
evitando un errore fatale che ne comprometta l'esecuzione.
Vengono implementate dalla sottoclasse Exception di Throwable.
public static void main(String[] args) {
int x = 3;
int y = 0;
int ris = x/y;
}
7. Eccezioni
public static void main(String[] args) {
int x = 3;
int y = 0;
try{
int ris = x/y;
}catch(ArithmeticException e){
System.out.println("DivBy 0! exception");
}
}
8. try - catch
Le eccezioni oltre che ad essere generate possono essere lanciate in situazioni
previste per poter reagire di conseguenza, possono essere create e manipolate.
I casi di eccezione possono essere gestiti in blocchi di codice di tipo try – catch.
All'interno del blocco try si inserisce il blocco di codice che potrebbe sollevare
un'eccezione, mentre i blocchi catch catturano una o più eccezioni, dalla meno
generica alla più generica (Exception).
I comandi per gestire le eccezioni sono: try , catch , finally , throw e throws
9. try
Nel blocco try si inserisce del codice che potrebbe sollevare eccezioni,
solitamente al fine di una gestione più lineare si consiglia di effettuare la
dichiarazione al di fuori e l'assegnamento al suo interno.
Qualsiasi istruzione, all'interno del blocco try, eseguita dopo quella che solleva
l'eccezione NON sarà raggiunta se l'eccezione sarà sollevata.
try{
int ris = x/y;
System.out.println("Non stampo in caso di DivBy 0");
}
10. catch
I blocchi di catch servono per catturare una o più eccezioni, anche qui vale
l'ereditarietà, se catturo prima un'eccezione più generica non potrò catturare
dopo quella più specifica, il compilatore darà errore!
Si può eseguire un solo blocco di catch alla volta!
Dopo un blocco di catch il programma riprenderà da dopo il blocco try e non da
dopo l'istruzione che ha generato l'errore!
...
}catch(ArithmeticException e){
System.out.println("DivBy 0! exception");
}catch(Exception e){
//TODO...
}
11. e.printStackTrace()
Lo stack è la sequenza di metodi richiamati per eseguire un'operazione. Il metodo
e.printStackTrace() serve a stampare l'ordine delle chiamate (con informazioni
aggiuntive) richiamate a partire dalla riga che ha generato l'errore.
Sono presenti il nome del metodo, il nome del file e la riga in cui è avvenuta
l'eccezione, l'insieme dei metodi che hanno portato all'esecuzione di
quest'ultimo, e il tipo di di eccezione con il relativo message.
12. finally
Un blocco di codice finally è definito sempre dopo l'ultimo blocco di catch
dichiarato. Non può esistere un try senza catch o senza un finally, posso avere
un blocco try/catch oppure un blocco try/finally oppure un blocco
try/catch/finally.
}catch(Exception e){
//TODO...
}finally{
System.out.println("istruzioni sempre eseguite");
}
13. throw
Le eccezioni possono essere comandate, in casi particolari potrebbe essere
necessario lanciare delle eccezioni e lo si può fare all'interno di un blocco try
creando una nuova eccezione e lanciarla con la keyword throw. Attenzione tutto
il blocco di codice interessato dopo la dichiarazione del lancio di una nuova
eccezione sarà codice irraggiungibile e pertanto il compilatore darà errore.
try{
throw new NullPointerException("Messaggio opzionale");
//int ris = x/y;
//System.out.println("Non stampo in caso di DivBy 0");
}catch(NullPointerException ex) {
System.out.println(ex.getMessage());
}finally {
System.out.println("Messaggio sempre stampato");
}
14. throws
Gli errori che vengono sollevati volontariamente possono essere catturati e
gestiti subito, grazie a un blocco di catch, oppure possono essere rimandati al
metodo chiamante, delegando al chiamante come comportarsi in caso di errore.
Il chiamante dovrà quindi o inserire il metodo che potrebbe generare l'errore in
un blocco try/catch e quindi potrà scegliere come gestire l'eccezione oppure
potrà a sua volta rilanciarla a chi lo ha chiamato, così via fino al sistema
operativo della JVM che bloccherà il processo che causa l'eccezione
interrompendo l'esecuzione del programma.
public static void main(String[] args) throws NullPointerException {
15.
16.
17. Errore
L'errore è una situazione imprevista non dipendente da un errore commesso dallo
sviluppatore. Gli errori non sono gestibili!
Vengono implementati dalla sottoclasse Error di Throwable.
18. Gerarchia
Errori ed eccezioni possono essere
categorizzate in checked e
unchecked exception.
Quelle in rosso
(RuntimeException e derivate)
sono situazioni non prevedibili
a priori, dipendono da eventi
esterni (input di utenti ecc..)
e sono dette UNCHECKED.
Tutte le altre son prevedili e
controllabili, vengono
chiamate CHECKED.
19. Errore ed Eccezione
Non bisogna confondere errori ed eccezioni:
Errore = situazione / problema che un programma non può risolvere
Eccezione = problema non critico, gestibile e prevedibile.
Sia errore che eccezione vengono "lanciati" e sono entrambi figli della classe
Throwable. In caso di lancio di un'eccezione la JVM crea un'oggetto della classe
Throwable (o derivata) e crea un rapporto dettagliato (stacktrace) sull'accaduto,
interrompe il flusso per poi riprenderlo dopo il catch.
Posso catturare più eccezioni in un blocco catch dividendole con " | " (>=java7).
Un blocco finally verrà sempre eseguito per ultimo, anche dopo il catch, anche in caso
non ci siano catch (try finally) in caso di eccezione il finally viene eseguito e dopo il
programma terminerà in modo anomalo.
catch(ArithmeticException | EmptyStackException ex) {
20. try with resources (>=Java7)
Meccanismo che permette la chiusura automatica degli oggetti che per essere usati
devono essere aperti (es: stream).
Gli oggetti che devono essere istanziati e aperti sono tutti dichiarati nel blocco di
parentesi tonde tra il try e il suo statement.
Tali oggetti non dovranno essere chiusi prima della fine del blocco try, ma è come
se i comandi di chiusura si trovassero automaticamente all'interno di un blocco
finally.
21. try with resources (>=Java7)
Se durante il try venisse sollevata un'eccezione essa verrebbe considerata come
prioritaria rispetto ad altre che potrebbero generarsi dalla chiusura degli oggetti
dichiarati dal blocco di reources.
Queste altre eccezioni verranno marcate come suppressed ma non verranno perse, ma
saranno accessibili come attributo dell'eccezione catturata col metodo
getSuppressed().
Gli oggetti utilizzabili dentro a un blocco resources devono obbligatoriamente
implementare l'interfaccia AutoCloseable o Closeable.
22. warnings
Non si tratta di veri e propri errori ma piuttosto di segnalazioni che alcuni
statement potrebbero portare, in altri contesti, a generare delle eccezioni che
non gestisco.
È possibile informare il compilatore per dirgli di ignorare uno o più warnings con
l'annotazione @SuppressWarning("nomeDelWarning") sulla classe che lo solleva.
@SuppressWarnings("serial")
23. throws e override
Quando si fa un override non è
possibile specificare clausole di
throws che non siano già
presenti in quello originale o
che non siano figlie della stessa
classe.
Devo sempre avere un
sottoinsieme delle eccezioni (o
al massimo uguali) a quelle del
padre. Il non dichiararla è un
sottoinsieme vuoto di quelle
del padre e quindi sempre
valido (ecco perché la
SottoClasseCorretta3 è
corretta!)
24. Eccezioni personalizzate
Per generare una class Exception personalizzata è sufficiente estendere la
classe Exception (o una sua eccezione).
È possibile, proprio come nelle classi, definire attributi e metodi
personalizzati.
In particolare si può ridefinire il metodo getMessage() o altri metodi comuni,
istanziare un logger per loggare in automatico le eccezioni.
public class RandomException extends Exception {
RandomException(){
super("Random Exception generated");
}
@Override
public String toString() {
return getMessage() + " : numero sfortunato";
}
}
25. Assertion
Le asserzioni devono il loro successo alla progettazione per contratto, ovvero una
tecnica di programmazione per la quale si testa l'applicazione in tre
stati(asserzioni): precondizione, postcondizione e invarianti. Molto usata per
scrivere test.
Precondizione = sviluppatore asserisce a come deve trovarsi l'applicazione prima
di invocare un'operazione (non confondere con eccezione!)
Postcondizione = sviluppatore asserisce a come dovrà essere lo stato di
un'applicazione al termine dell'esecuzione di un'operazione. È un modo utile per
asserire a cosa fare ma non dice il come.
Invariante = serve a specificare vincoli su oggetti istanziati (consente stati di
inconsistenza temporanei, ma poi deve tornare consistente)
26. Asserzione
L'asserzione è invece una condizione di verità che deve sempre essere verificata. Essa
rappresenta uno strumento per testare la robustezza del software.
Le asserzioni possono essere abilitate in fase di sviluppo o test e disabilitate in fase di
produzione affinché il programma non subisca rallentamenti.
In caso di asserzione non verificata l'esecuzione verrà interrotta.
Per generare un'asserzione la si
dichiara con la keyword assert.
(lanciare la VM col parametro –ea )
(-ea è enableassertion)
int y = 0;
assert y!=0;
27. assert
La parola chiave assert serve per asserire un valore di verità. Per poter
funzionare si deve avviare la JVM con il parametro –ea. Con -da si
disabilitano, ma basta avviarla senza parametri dato che di default sono
disabilitate.
Con il comando -esa si abilitano le asserzioni anche nelle librerie di sistema.
È possibile abilitare e disabilitare le asserzioni anche a livello di package
Permette di definire un vincolo di verità che se non verificato genera un
errore
28. Interfaccia funzionale
Interfaccia che contiene un solo metodo ASTRATTO (detto anche SAM acronimo di
Simple Abstract Method).
Un'interfaccia è funzionale anche se ha altri metodi ma marcati come default con
un corpo proprio, l'importante è che sia UNO solo quello astratto.
Java8 ha un package con sole interfacce funzionali: java.lang.function
Un'interfaccia funzionale viene spesso implementata da una funzione lambda
29. Lambda
Un espressione lambda è detta anche funzione anonima (anonymous function),
non è un metodo appartenente ad una classe e chiamato tramite un oggetto, ma
una funzione senza nome definita al volo in maniera simile come le classi
anonime.
Solitamente si usa per implementare un'interfaccia funzionale al volo.
30. Classe anonima
Prima quando dovevamo implementare una classe anonima scrivevamo:
new Thread(new Runnable){
@Override public void run(){
System.out.println("di Java 8: Classe anonima ");
}
}).start();
31. Lambda
Grazie alle lambda possiamo dimezzare il codice rimuovendo la parte che può
dedurre tranquillamente il compilatore, possiamo avere una sintassi di questo
tipo:
ha la stessa valenza della classe anonima scritta precedentemente riducendo il
nostro codice da 5 linee a un'unica linea di codice.
new Thread(() -> System.out.println("Java 8: Funzione anonima")).start();
32. Espressione Lambda
L'espressione lambda può essere referenziata con una reference di un interfaccia
funzionale, e la sua definizione non è altro che l'implementazione dell'unico
metodo astratto dell'interfaccia funzionale, la nostra espressione lambda in
sostanza può sostituire un'interfaccia funzionale.
Si concentra su cosa fare e non sul come farlo.
Riduzione del codice
33. Espressione Lambda
Riduzione del codice
È possibile omettere parentesi tonde o graffe ove fossero necessarie per riuscire
a ridurre il tutto a una sola linea di codice, questo solo se la riga di codice
prevede una sola e unica istruzione, altrimenti bisognerà utilizzare le parentesi
dove ne necessitiamo.
34. Espressione Lambda: sintassi
parametro1 -> comando(parametro1)
(Tipo parametro1) -> comando(parametro1)
(parametro1,parametro2) -> {implementazione con return}
Il simbolo "->" serve a dividere i parametri (numero e tipo automaticamente
dedotti) dall'implementazione, che se monofunzione non richiede parentesi
graffe e NON vuole il return (errore di compilazione), in caso di più
funzioni/operazioni le graffe e il return diventano obbligatorie; anche return;
senza nulla è valido in caso di void.
35. Espressione Lambda: visibilità
All'interno di una classe anonima, il reference this si riferisce all'oggetto corrente
istanziato dalla classe anonima, e per riferirsi ai membri della classe che include
la classe anonima dobbiamo utilizzare una sintassi di questo genere:
NomeCasseEsterna.this.nomeMembro
con l'espressione lambda invece, il reference this si riferisce alla classe in cui è
inclusa l'espressione.
Vedi esempio seguente
36. Espressione Lambda: visibilità
public class LambdaThis{
private String stringa = "Variabile d'istanza di classe";
public void metodoContenenteLambda(){
String stringa = "variabile locale del metodo contenente";
new Thread(()-> System.out.println(this.stringa).start());
new Thread(()-> System.out.println(stringa).start());
}
public static void main(String[] args) {
LambdaThis lambdaThis = new LambdaThis();
lambdaThis.metodoContenenteLambda();
}
}
37. Espressione Lambda: visibilità
Per quanto riguarda le variabili locali dichiarati all'interno di un espressione
lambda, esse condivido lo stesso scope (visibilità) con il blocco di codice dove
sono definite.
Come le classi anonime anche le espressioni lambda possono utilizzare le variabili
esterne solo se sono state dichiarate final o sono effettivamente non
modificabili.
In molti casi il compilatore si accorge se tentiamo di modificare una variabile
dichiarata esternamente. Un trucco per “ingannare” il compilatore c'è, infatti
benché una variabile dichiarata final, e quindi non modificabile, possiamo
“wrappare” il valore della nostra variabile, modificando la struttura interna,
senza che crei problemi al compilatore.
38. Espressione Lambda: eccezioni
Quando utilizziamo le espressioni lambda bisogna fare attenzione alle eccezioni
cheked exception, ovvero eccezioni che NON sono sottoclassi di
RuntimeException.
Per sviare questo problema bisognerebbe usare il throws della dichiarazione del
metodo che potrebbe lanciare l'eccezione nel caso non siano gestite.
Purtroppo le lambda non dichiarano metodi, e non sarebbe possibile utilizzare la
clausola throws se non nel metodo dell'interfaccia funzionale che rappresentano.
39. Espressione Lambda: eccezioni
new Thread(() -> {
Thread.sleep(1000);
System.out.println("Hello World");
}).start();
questo tipo di codice darà luogo ad un errore di compilazione, e per risolverlo
abbiamo due possibilità:
gestire tutto con il try –catch
utilizzare un'interfaccia funzionale che definisca il metodo astratto con la
clausola throws richiesta
40. Espressione Lambda
L'obbiettivo delle espressioni lambda è il passaggio dinamico di algoritmi
implementati "al volo" come parametri di metodi.
Di fatto una lambda è la sola implementazione di un'interfaccia funzionale
quando necessario.
41. Espressione Lambda: referenza a
metodo statico
Un modo per implementare una lambda è l'utilizzo della chiamata ai metodi
statici (referenza a metodo).
NomeClasse : : nomeDelMetodoStatico
Quando si ha un metodo statico che accetta un solo parametro il compilatore è in
grado di autodedurre tutte le informazioni necessarie (tipo e ordine).
public class Filtri{
public static boolean isFilmDiFantascienza(Film film){
return "Fantascienza".equals(film.getGenere());
}
}
...
Film[] filmDiFantascienza =
videoteca.getFilmFiltrati(Filtri::isFilmDiFantascienza);
42. Espressione Lambda: referenza a
metodo d'istanza
Stesso principio precedente applicato però non più su una classe ma su un'istanza
(oggetto) di essa.
NomeOggetto : : nomeDelMetodoDIstanza
public class OridnamentoFilm {
public int ordinamentoPerNome(Film film, Film film1){
return film.getNome().compareTo(film1.getNome());
}
}
...
Film[] catalogo = videoteca.getFilm();
OrdinamentoFilm of1 = new OrdinamentoFilm();
...
Arrays.sort(catalogo, of1::ordinamentoPerNome);
43. Espressione Lambda: referenza a
metodo d'istanza di un certo tipo
Stesso principio dei precedente applicato però a un metodo non statico di una
classe senza però averne un'istanza.
NomeClasse : : nomeDelMetodoDIstanza
Il metodo di istanza è un metodo non statico, ma richiamato attraverso la classe.
Collections.sort(myCollection, String::compareToIgnoreCase());
44. Espressione Lambda: referenza a
costruttore
In questo caso non ci si riferisce a un metodo statico in quanto il costruttore
rappresenta un'oggetto. Si otterrà un'istanza di quella classe. Mi permette di
evitare classe Factory.
NomeClasse : : new
46. Interfaccia Predicate
Un metodo astratto (è funzionale!) test()
Accetta un predicato (confronto)
Ritorna un booleano (Ottima per i test)
Ideale per filtrare gli oggetti
Possibilità di concatenare metodi: or(), xor(), e and()
47. Interfaccia Consumer
Un metodo astratto (è funzionale!) accept()
Accetta un predicato (confronto)
Ritorna un void
Utile a cambiare lo stato interno dell'oggetto passato / consumarlo
Possibilità di concatenare metodi: andThen()
48. Interfaccia Supplier
Un metodo astratto (è funzionale!) get()
Ritorna un'istanza del tipo generico con la quale è dichiarata
Utile a creare istanze / factory methods
49. Interfaccia Function
Un metodo astratto (è funzionale!) apply()
Permette di astrarre il concetto di funzione
Utile a modificare il dato
50. Interfaccia Bi-Function
Un metodo astratto (è funzionale!) apply()
Permette di astrarre il concetto di funzione a due parametri
Utile a modificare i dati, ritornando un solo dato
Esistono anche Bi-Supplier e Bi-Predicate
51. Interfaccia Unary Operator
Un metodo astratto (è funzionale!) apply(), ereditato da Function
Permette di astrarre il concetto di trasformazione sul parametro
Utile a trasformare un dato in ingresso
Ritorna lo stesso tipo in ingresso
Utile per combinare più espressioni lambda dello stesso tipo
52. Stream
Lo Stream è una nuova interfaccia definita all'interno del package
java.util.stream
rappresenta un flusso che ha come sorgente una collezione di dati, e sul quale
si possono eseguire operazioni, facendo utilizzo delle lambda e reference a
metodi
Lo stream solitamente viene creato a partire da una determinata collezione,
tramite la chiamata del metodo stream().
53. Stream
È possibile istanziare uno stream passando un array al metodo statico of():
Stream <String> stringStream=Stream.of(arrayDiStringhe);
Passando all'altra versione del metodo of() direttamente dei valori:
Stream<String> stringStream=Stream.of(“Take”,“The”,“Time”);
Tramite il metodo statico generate() delle stessa classe Stream, ci fornisce uno stream
infinito perché ogni volta che vogliamo un valore dallo stream ne verrà creato uno:
Stream<Double> randomDouble=Stream.generate(Math.random);
Questo esempio di codice usando una reference al metodo random() della classe Math
restituisce un numero casuale di tipo double maggio o uguale di 0.0 e minore di 1.
54. Stream
È possibile iterare sulle collezioni utilizzando un' oggetto Stream, che definisce anche
un metodo forEach(), e da esso possiamo usufruire delle cosiddette “operazioni di
aggregazione” utilizzate in combinazione alle espressioni Lambda. È anche possibile
concatenare le operazioni per creare delle pipeline.
Lo stream non immagazzina elementi, ma permette di operare su di essi attraverso
una serie di operazioni: le pipeline
Smartphones
.stream()
.filters(s ->”Sasmsung”.equals(s.getMarca())
.forEach(s → Sout(s));
55. Pipeline
Rappresenta un insieme di operazioni da eseguirsi su un flusso dati ( stream ).
Le operazioni per creare ed usare una pipeline sono;
Aprire una sorgete (ottenere uno stream)
Eseguire zero o più operazioni di aggregazione / riduzione / intermedie
Eseguire un metodo / operazione terminale per usare/raccogliere i dati modificati.
57. Pipeline: 3 componenti fondamentali
Una sorgente: potrebbe essere una collezione o un array, o altri canali di
input/output, oppure il metodo generate() della classe Stream stessa, quindi per
ottenere un oggetto Stream la sorgente deve mettere a disposizione un metodo.
Zero o più operazioni di aggregazione (o intermedie): queste tipi di operazione
hanno la caratteristica di ritornare un nuovo oggetto Stream, esempio il metodo
filter(). Altri tipi di metodi che abbiamo a disposizione sono map(), mapToDouble().
Questi e tanti altri metodi intermedi sono chiamati “metodi lazy(pigri)”, perché
vengo chiamati all'ultimo momento, ovvero saranno invocati solo quando sarà
invocato il metodo terminale, che ovviamente ha bisogno di metodi intermedi per
essere eseguito.
Un' operazione terminale: metodo che restituisce un risultato che non è oggetto
Stream, ad es: Collect.
58. Optional
Consente di evitare i NullPointerException nel caso in cui una pipeline non
ritorni nulla.
Esistono OptionalDouble, OptionalLong, OptionalInt e Optional<T>
Potrebbero sollevare NoSuchElementException
Facilmente evitabile usando il metodo .orElse() che specifica cosa fare in caso
in cui l'optional sia vuoto
60. Reduction Operation
metodo che a partire dagli elementi di uno stream, ritorna un solo valore.
Le tipiche implementazioni di operazione di riduzione sono min(), max(),
sum(), average(), count() ...
Altre operazioni di riduzioni possono tornare delle collezioni, es reduce() e
collect()
61. Metodo Reduce
Esiste in più versioni (overload), la più famosa ha:
- Un oggetto detto identity che rappresenta il valore iniziale
- Un oggetto BinaryOperator (accumulator) che rappresenta un'espressione
lambda a due parametri
Il valore di reduce ritorna ad essere come il primo argomento della BiFunction.
62. Metodo Collect
Usato con i metodi statici della classe Collectors, anch'esso esiste in più versioni
(overload):
- Possiamo ottenere liste con Collectors.toList() (si duplicati)
- Possiamo ottenere dei Set (no duplicati)
- Consente operazioni di reducing(), groupingBy(), toList(), toCollection()