Java I nuovi paradigmi e linguaggi tendono a semplificare il lavoro del programmatore, nascondendo dentro le librerie (o i costrutti del linguaggio) parte della programmazione necessaria Da Java 5, in un ciclo, si può scorrere una lista o un array senza dover dare il valore di inizio e fine dell indice List<String> nomi;... for (String nome : nomi) {... Da Java 7, si può evitare di indicare il tipo durante l istanziazione di sottotipi di Collection, lasciando al compilatore l inferenza List<String> nomi = new LinkedList<>(); Java è un linguaggio imperativo, tuttavia Java 8 include caratteristiche tipiche della programmazione funzionale. La programmazione funzionale è in genere più concisa, espressiva, e facile da parallelizzare rispetto alla programmazione ad oggetti Classi Anonime In Java Da Java 1.1 si possono usare classi anonime per implementare interfacce public interface Hello { public interface Hello { public void greetings(string s); public void greetings(string s); public class Sera { public class Sera { private Hello myh; private Hello myh; public Sera(Hello h) { public Sera(Hello h) { myh = h; myh = h; public void saluti() { public void saluti() { myh.greetings("buonasera"); myh.greetings("buonasera"); public class Saluti implements Hello { public class MainSaluti { @Override public static void main(string[] args) { public void greetings(string s) { Sera ser = new Sera(new Hello() { System.out.println("Ciao, "+s); @Override public void greetings(string s) { System.out.println("Ciao, "+s); public class MainSaluti { public static void main(string[] args) { ); Sera ser = new Sera(new Saluti()); 1 2 Implementazione Interfaccia La classe anonima implementa il metodo dell interfaccia, e tale codice viene dato al momento dell istanziazione Sera ser = new Sera(new Hello() { @Override public void greetings(string s) { System.out.println("Ciao, "+s); ); Java 8 permette di evitare di passare i preamboli (o codice standard, boilerplate), e di implementare solo il comportamento nuovo, se l interfaccia ha un solo metodo // Java 8 Sera ser = new Sera(s -> System.out.println("Ciao, " + s)); Si implementa solo il codice del corpo del metodo Espressioni Lambda s -> System.out.println("Ciao, " + s) Il codice sopra è un espressione lambda Un espressione lambda è una funzione anonima, che prende in ingresso zero o più parametri, a sinistra del segno freccia, fra parentesi tonde, e un blocco di codice, a destra del segno freccia Un espressione lambda che prende in ingresso due parametri è (x, y) -> x + y I parametri in ingresso sono x e y, a sinistra della freccia, il cui tipo è determinato automaticamente, nella maggior parte dei casi Il codice a destra della freccia x + y implementa la somma dei due parametri in ingresso, il valore risultante viene restituito 3 4
Design Pattern Observer // ConcreteSubject public class Note extends Observable { private String n = "Una nota"; public void aggiungi() { setchanged(); notifyobservers(n); Si crea un istanza del ConcreteSubject Note Note n = new Note(); Si passa il comportamento del ConcreteObserver nel momento in cui si chiama n.addobserver() sul ConcreteSubject n n.addobserver( (s, o) -> System.out.println("Osservatore di "+s.getclass()+" per "+o)); Chiamando il metodo n.aggiungi() si ha l output Osservatore di class Note per Una nota public class Trova { private List<String> nomi = Arrays.asList("Nobita", "Nobi", "Suneo", "Honekawa", "Shizuka", "Minamoto", "Takeshi", "Gouda"); // in stile imperativo public void trovaimper() { boolean trovato = false; for (String nome : nomi) if (nome.equals("nobi")) { trovato = true; break; if (trovato) System.out.println("Nobi trovato"); else System.out.println("Nobi non trovato"); // in stile dichiarativo public void trovadichiar() { if (nomi.contains("nobi")) System.out.println("Nobi trovato"); else System.out.println("Nobi non trovato"); 5 6 Considerazioni L implementazione imperativa si serve di una variabile boolean come flag per indicare se l elemento è stato trovato. Inoltre si usa un ciclo per scorrere la lista e controllare se uscire dal ciclo Lo stile imperativo dà al programmatore il controllo di quel che il programma deve fare, tuttavia Bisogna implementare varie linee di codice, e molto spesso si hanno cicli che scorrono liste per trovare valori, calcolare somme, etc. Per capire cosa si vuol fare, dobbiamo prima leggere tanti dettagli all interno del corpo del ciclo Il ciclo è esterno al codice che implementa la lista (l iterazione è esterna) Stile Funzionale Lo stile di programmazione funzionale è dichiarativa. La programmazione funzionale aggiunge allo stile dichiarativo funzioni di ordine più alto In Java si possono passare oggetti ai metodi, creare oggetti dentro i metodi, e ritornare oggetti dai metodi In Java 8 si possono passare funzioni ai metodi, creare funzioni dentro i metodi e ritornare funzioni dai metodi Un metodo è una parte di una classe, mentre una funzione non è associata ad una classe Un metodo o una funzione che ricevono, creano o ritornano una funzione, si considerano essere funzioni di ordine più alto Nella versione dichiarativa alcuni dettagli dell implementazione sono nella libreria sottostante, l iterazione è interna 7 8
Java Collection Le librerie di Java hanno tante interfacce e classi collection. Collection è un interfaccia che definisce i metodi add(), remove(), size(), contains(), containsall(), etc. List definisce i metodi get(), indexof(), set(), etc. get add contains remove ArrayList costante costante O(n) O(n) LinkedList O(n) costante O(n) costante TreeSet O(lg n) O(lg n) <<interface>> List <<abstract>> AbstractList <<interface>> Collection <<interface>> Set <<abstract>> AbstractSet Java 8 Java 8 introduce i metodi di default per le interfacce Ciò ha permesso di avere metodi nuovi per le interfacce esistenti, senza compromettere la compatibilità delle applicazioni conformi a precedenti versioni di Java stream() è un metodo di default dell interfaccia Collection I metodi di default non agiscono sullo stato, invece le classi astratte che hanno metodi implementati possono avere costruttori e metodi che agiscono sullo stato LinkedList ArrayList Vector TreeSet ArrayList è un array espandibile, ovvero cresce del 50% quando non vi è più spazio. Gli elementi sono contigui, quindi l accesso è veloce LinkedList è una lista, ogni elemento contiene un riferimento al successivo e al precedente Vector è simile ad ArrayList, ma è synchronized 9 10 Stile Funzionale // in stile funzionale private void trovafunc() { if (nomi.stream().filter(s -> s.equals("nobi")).count() > 0) System.out.println("Nobi trovato"); else System.out.println("Nobi non trovato"); stream() è un metodo di default dell interfaccia Collection Permette di costruire operazioni complesse con un approccio funzionale Restituisce un oggetto Stream che non è una nuova collezione, ma dà un modo per creare una collezione Sull oggetto Stream si possono chiamare varie operazioni (per es. filter()), ed alcune di esse accettano funzioni come argomenti 11 Filter nomi.stream().filter(s -> s.equals("nobi")).count() A filter() di Stream si passa la funzione s -> s.equals("nobi") L espressione lambda (o funzione) s -> s.equals("nobi") Ha il parametro in ingresso s che è un elemento della Collection nomi Restituisce un boolean Il valore di ritorno di filter() è uno stream che contiene solo gli elementi dello stream iniziale che soddisfano la condizione fornita filter() è lazy, ovvero operazione intermedia, non crea un valore in uscita, se non necessario count() è eager, ovvero operazione terminale, genera un valore e forza l esecuzione delle precedenti operazioni Tutte le operazioni che restituiscono uno Stream sono lazy 12
public class Pagamenti { private List<Float> importi = new ArrayList<>(); // in stile imperativo public float calcolasommaimper() { float risultato = 0; for (float v : importi) risultato += v; return risultato; Reduce importi.stream().reduce(0f, (v, accum) -> accum + v); reduce() di Stream applica la funzione che gli viene passata (secondo argomento), in questo caso la somma, a tutti gli elementi dello stream e accumula un risultato // in stile funzionale public float calcolasomma() { return importi.stream().reduce(0f, (v, accum) -> accum + v); ArrayList è un implementazione di List basata su array di dimensione variabile, i metodi get() e add() eseguono in un tempo costante, quando l array ha ancora spazio (altrimenti prima della add bisogna espandere l array) reduce() è eager accum = 0f; for (v : stream) accum += v; return accum; L espressione lambda (v, accum) -> accum + v Implementa la somma di ciascun valore v dello stream con la variabile accum, che inizialmente vale 0f (vedi primo parametro di reduce()) e successivamente vale la somma precedentemente calcolata 13 14 public class Person { private String nome; private int eta; public Person(String n, int e) { nome = n; eta = e; public String getnome() { return nome; public int geteta() { return eta; new Person("Taro", 21), new Person("Ian", 19), new Person("Al", 16)); Calcoliamo la somma delle età in stile funzionale int somma = p.stream().map(person::geteta).reduce(0, (v, t) -> v + t); 15 int somma = 0; for (Person x : p) somma += x.geteta(); Map int somma = p.stream().map(person::geteta).reduce(0, (x, t) -> x + t); map() di Stream restituisce uno stream contenente i risultati dell esecuzione della funzione passata come argomento di map(), in questo caso geteta(), su tutti gli elementi dello stream iniziale map() è lazy Ovvero, a ciascun elemento dello stream iniziale map() fa corrispondere un risultato. In questo caso, ogni elemento dello stream iniziale è un istanza di Person, map() restituisce uno stream contenente il valore del campo eta di ciascuna istanza di Person La funzione che si passa a map() è il metodo geteta() di Person, la sintassi è Person::getEta 16
Collect new Person("Taro", 21), new Person("Ian", 19), new Person("Al", 16)); Ricaviamo la lista delle età List<Integer> e = p.stream().map(x -> x.geteta()).collect(collectors.tolist()); Come prima, map() restituisce uno stream con i valori delle età, e l espressione lambda passata dice come trasformare ciascun elemento dello stream L operazione collect() di Stream permette di raggruppare i risultati e prende in ingresso un Collector. La classe Collectors implementa metodi utili per raggruppamenti, il metodo tolist() restituisce un Collector che accumula elementi in una List Programmazione Parallela I processori attuali hanno vari core, due per i portatili, quattro per i desktop, dodici per i server La programmazione parallela è più difficile di quella sequenziale e usare bene l hardware risulta più complicato In Java, la classe Thread permette di lanciare un nuovo thread di esecuzione, ma spesso bisogna risolvere i problemi di corsa critica, usando opportunamente synchronized, wait, notify Java 5 mette a disposizione Lock, Esecutori, etc. Le operazioni map(), filter(), etc. di Stream eseguono sequenzialmente, su un unico core, o in parallelo? Le operazioni map() e filter() di Stream sono stateless, ovvero non tengono uno stato durante l esecuzione, quindi il risultato non dipende dall ordine in cui i singoli elementi su cui eseguono vengono prelevati List<Integer> e = new LinkedList<>(); for (Person x : p) Questo facilita grandemente la parallelizzazione e.add(x.geteta()); 17 18 Stream Paralleli Collection ha i metodi di default stream() e parallelstream() Il metodo parallelstream() possibilmente dà uno stream parallelo. Quindi per avere l esecuzione parallela basta usare il metodo parallelstream() (niente più bisogno di thread e sincronizzazione, per molti casi) Le prestazioni dipendono da: (i) numero di elementi dello stream, (ii) operazioni da svolgere, (iii) hardware, e (iv) tipo di Collection su cui si invoca l operazione Dichiarativo O Funzionale? Data la lista con le istanze di Person new Person("Taro", 21), new Person("Ian", 19), new Person("Al", 16)); Lo stile dichiarativo non funzionerebbe if (p.contains("saro")) System.out.println("Saro trovato"); Poiché la lista p contiene istanze di Person e non valori String La Collection potrebbe essere ArrayList, LinkedList, etc., con tempi di accesso diversi (vedere tabella precedente) Lo stile funzionale consente di estrarre il campo nome, inoltre permette di valutare una funzione ad-hoc, quindi è molto più flessibile e potente Eseguendo su milioni di elementi il seguente codice, le prestazioni migliorano (su hardware con più core) per ArrayList, Vector, TreeSet; p.stream() non migliorano quando si usa LinkedList a causa dell accesso sequenziale.filter(s -> s.getnome().equals("saro")) c = nomi.parallelstream().count().map(s -> s.touppercase()).filter(s -> s.equals( NOBI )).count(); 19 20
Esercizio: Ricerca Su Lista Data la lista di istanze di Person, trovare il nome della persona che è più grande (di età) fra quelli che hanno meno di 20 anni new Person("Taro", 21), new Person("Ian", 19), new Person("Al", 16)); In versione imperativa, se volessimo scorrere la lista solo una volta private void trovapersonaimper() { Person pmax = null; for (Person x : p) if (x.geteta() < 20) { if (pmax == null) pmax = x; if (pmax.geteta() < x.geteta()) pmax = x; if (pmax!= null) System.out.println("persona: " + pmax.getnome()); Ricerca Su Lista La versione funzionale è private void trovapersona() { Optional<Person> pmax = p.stream().filter(x -> x.geteta() < 20).max(Comparator.comparing(x -> x.geteta())); if (pmax.ispresent()) System.out.println("persona: " + pmax.get().getnome()); filter() separa gli elementi che soddisfano la condizione sull età, prende in ingresso la funzione da eseguire sugli elementi dello stream max() trova il valore massimo, è eager, prende un Comparator, restituisce un Optional, max() opera in modo simile a reduce() L operazione comparing() di Comparator prende una funzione che estrae una chiave e restituisce un Comparator Optional è un tipo di Java 8, rappresenta un valore che può esistere o meno, se esiste il metodo ispresent() darà un valore true, per estrarre il valore si usa get() Il corpo del ciclo ha varie condizioni, queste rendono il codice più difficile da comprendere 21 22 Conta Parole Data una stringa contare le occorrenze di ciascuna parola String ha il metodo split() che prende un espressione regolare e restituisce un array di stringhe in cui ciascun elemento è una parte della stringa iniziale HashMap è una tabella per coppie chiave - valore, può contenere solo una volta una certa chiave. Il metodo get() permette di prelevare il valore, data la chiave. Il metodo put() inserisce la coppia chiave - valore String frase = "ci sono tre mele sul tavolo e tre mele nella cesta"; List<String> l = Arrays.asList(frase.split("[\\s+,;]+")); La versione funzionale Conta Parole Map<String, Long> mp = l.stream().collect(collectors.groupingby(s -> s, Collectors.counting())); Collectors ha metodi utili che permettono di raggruppare elementi (abbiamo visto precedentemente il metodo tolist()) Il metodo groupingby() prende in ingresso una funzione per la classificazione, come primo argomento, e un Collector come secondo argomento Map<String, Long> mp = new HashMap<>(); for (String p : l) { if (mp.containskey(p)) mp.put(p, mp.get(p)+1); else mp.put(p, 1l); Il metodo counting() restituisce un Collector, che è una operazione di tipo reduce, ovvero conteggio di elementi con la stessa chiave Il metodo collect() che prende il suddetto Collector genera una HashMap con coppie chiave, ovvero parola, e valore, ovvero conteggio occorrenza 23 24