Sommario 1 2 3 Oggetti e classi.............................................................................................................................4 1.1 Definizione di oggetto e di classe..........................................................................................4 1.2 concetto di istanza di oggetto................................................................................................4 1.3 Invocazione di metodi............................................................................................................4 1.4 Parametri di un metodo..........................................................................................................4 1.5 Tipi di dati..............................................................................................................................4 1.6 Istanze multiple di una classe................................................................................................4 1.7 Stato di un oggetto.................................................................................................................4 1.8 Cos'è un oggetto?...................................................................................................................4 1.9 Il codice Java.........................................................................................................................4 1.10 Interazione di oggetti.........................................................................................................4 1.11 Codice sorgente..................................................................................................................4 1.12 Esercizio Lab-classes.........................................................................................................4 1.13 Valori restituiti da un metodo.............................................................................................5 1.14 Oggetti come parametri......................................................................................................5 Comprendere la definizione delle classi.......................................................................................5 2.1 Una macchina che stampa biglietti........................................................................................5 2.2 Metodi accessors e mutators..................................................................................................6 2.3 Interazione con la console.....................................................................................................6 2.4 Esercizi sulle classi.............................................................................................9 2.5 Per ripassare................................................................................................................9 2.6 Esercizi di fine capitolo.......................................................................................................10 Interazione tra oggetti.................................................................................................................10 3.1 L'orologio digitale................................................................................................................10 Esperimenti.................................................................................................................................11 4 3.2 Class Diagram vs. Object Diagram.....................................................................................12 3.3 Object type e primitive type................................................................................................12 3.4 Gli oggetti posso creare altri oggetti....................................................................................14 3.5 Overloading dei metodi e dei costruttori.............................................................................14 3.6 Esercizi di potenziamento....................................................................................................15 3.7 Uso del riferimento this....................................................................................................15 3.8 Uso del debugger in BlueJ...................................................................................................15 3.9 Per ripassare.........................................................................................................................16 Raggruppamento di oggetti........................................................................................................17 4.1 Music-organizer-v1......................................................................................................17 4.2 Music-organizer-v2......................................................................................................19 Eseguire i brani musicali mediante un oggetto MusicPlayer.....................................................19 4.3 Music-organizer-v3......................................................................................................20 Processare tutti gli elementi di una collection............................................................................20 ciclo predeterminato - for-each loop......................................................................................21 4.4 Music-organizer-v4......................................................................................................22 Cicli indeterminati – while.....................................................................................................22 4.5 Music-organizer-v5......................................................................................................23 Un miglioramento del progetto del MusicOrganizer: classe Track............................................23 4.6 Scorrere gli elementi di una collection con un iteratore......................................................25 4.7 Modalità di iterazione in Java (riassunto)............................................................................26 4.8 Esercizi con le classi............................................................................................................26 Progetto Club..............................................................................................................................26 Progetto Music-Organizer-v5e (con shuffle)..............................................................................26 Copia di un ArrayList in un altro ArrayList...........................................................................30 Ancora sul progetto Club...........................................................................................................30 Progetto Products (StockManager, Product, etc.)......................................................................30 4.9 Collections a dimensione fissa: gli array.............................................................................31 Alcuni esempi.............................................................................................................................31 4.10 Passaggio dei parameteri in java......................................................................................32 Passing Primitive Data Type Arguments....................................................................................32 Passing Reference Data Type Arguments...................................................................................32 4.11 5 Per ripassare.....................................................................................................................33 Oggetti con comportamento complesso.....................................................................................34 5.1 Un risponditore automatico completo: tech-support-complete...........................................34 5.2 Un risponditore banale: tech-support1.................................................................................34 5.3 La documentazione Java......................................................................................................34 Concetto di interfaccia e di implementazione di una classe.......................................................35 5.4 Utilizzo delle funzioni di libreria.........................................................................................35 5.5 Utilizzo di package, import e archivi jar.............................................................................36 Organizzazione delle classi in package......................................................................................36 Creazione di archivi jar..............................................................................................................38 5.6 Uso di mappe per creare associazioni..................................................................................38 Concetto di Map: la classe HashMap.........................................................................................38 Concetto di Set: classe HashSet.................................................................................................41 Suddivisione di stringhe.............................................................................................................41 Esercizi.......................................................................................................................................42 5.7 La documentazione delle classi...........................................................................................43 Uso di javadoc............................................................................................................................43 getImage.................................................................................................................................44 Public vs. Private........................................................................................................................44 Information Hiding.....................................................................................................................45 La parola chiave static................................................................................................................45 Le costanti – static final..............................................................................................................46 5.8 6 Per ripassare.........................................................................................................................46 Il progetto delle classi (cenni) - Design gudelines.....................................................................47 6.1 Dipendenza (coupling) e coesione (cohesion).....................................................................47 Coupling (the lower is better).....................................................................................................47 Cohesion (the higher is better)...................................................................................................47 6.2 Duplicazione del codice (da evitare)...................................................................................47 6.3 Incapsulamento e dipendenza (information hiding enforces decoupling)...........................48 6.4 Responsability driven design (to redouce coupling)...........................................................48 6.5 Refactoring..........................................................................................................................48 Refactoring per supporto multilingua (esempio)........................................................................48 6.6 Metodi statici (keyword static)............................................................................................49 Il metodo Main (rivisitato).........................................................................................................50 La classe Math............................................................................................................................50 6.7 7 Per ripassare.........................................................................................................................51 Testing e debugging....................................................................................................................52 7.1 Testing con BlueJ.................................................................................................................52 Test positivi e test negativi.........................................................................................................53 7.2 Test automatici.....................................................................................................................54 JUnit...........................................................................................................................................54 Test Fixture.................................................................................................................................57 8 7.3 Debugging............................................................................................................................57 7.4 Per ripassare.........................................................................................................................58 Riferimenti per Java...................................................................................................................58 In queste note si seguirà l'approccio del testo Objects First with Java i cui esempi (utilizzati in queste note) sono liberamente scaricabili al link http://www.bluej.org/objectsfirst/resources/projects.zip . 1 Oggetti e classi 1.1 Definizione di oggetto e di classe 1.2 concetto di istanza di oggetto Esempio figures → new Circle() 1.3 Invocazione di metodi (la comunicazione con gli oggetti). Metodo makeVisible() sull'istanza cicle1. 1.4 Parametri di un metodo Segnatura di un metodo. 1.5 Tipi di dati I tipi di dati dei parametri di un metodo. 1.6 Istanze multiple di una classe 1.7 Stato di un oggetto 1.8 Cos'è un oggetto? Un oggetto di una classe ha i campi della classe e i metodi di quella classe. 1.9 Il codice Java Un programma contiene o usa dichiarazione di classi; crea oggetti e effettua chiamate di metodi su oggetti. Uso del code panel di blueJ. 1.10 Interazione di oggetti Utilizzo dell'esempio house. 1.11 Codice sorgente La scrittura delle classi: analisi del codice sorgente. Vedere il codice delle classi Person e Circle. Esercizi 1.18, 1.19, 1.20 1.12 Esercizio Lab-classes Esercizio lab-classes 1.13 Valori restituiti da un metodo 1.14 Oggetti come parametri Con la classe lab-class creare una classe e iscrivere alcuni studenti alla classe. Con questo esempio possiamo osservare che in Java è possibile passare come parametri ai metodi di una classe i riferimenti a oggetti di altre classi. 2 Comprendere la definizione delle classi 2.1 Una macchina che stampa biglietti Riprendiamo i concetti già visti nel capitolo precedente con un altro esempio guida: la classe naive-ticket-machine . Creare un oggetto di macchina che stampa biglietti. Analizzare i limiti di questa implementazione. Cosa succede se mettiamo più soldi del costo del biglietto? E se mettiamo meno soldi? Come fare a stampare biglietti di taglio diverso? Analizziamo il codice della classe e osserviamo alcuni aspetti fondamentali: class header: public class TicketMachine { Inner part of the class omitted. } field, constructors, methods: public class ClassName { Fields Constructors Methods } Attenzione alla scelta dei nomi di variabili: devono essere scelti in modo da rendere il più possibile chiaro il significato di una variabile. I metodi: public class TicketMachine { Fields omitted. Constructor omitted. /** * Return the price of a ticket. */ public int getPrice() { return price; } Remaining methods omitted. } Un metodo che non restituisce alcun valore ha un tipo di ritorno void e un'istruzione return seguita da un punto e virgola: public void Print() { //some statement return; } 2.2 Metodi accessors e mutators I metodi che accedono alle variabili di istanza (i campi di classe) si dicono metodi accessors e hanno un nome del tipo getNomeCampo(). Ad esempio getPrice(), oppure getBalance(). I metodi che impostano una variabile d'istanza si dicono mutators e tipicamente hanno un nome del tipo setNomeCampo(); public int getPrice() { return price; } public void setPrice(int cost) { price = cost; } 2.3 Interazione con la console Stampare il biglietto con il metodo printTicket(). Stampare su console: System.out.println("Stringa da stampare tra doppi apici"); String parole = "qualcosa da dire"; String paroleSensate = "altrimenti meglio tacere"; System.out.println(parole +"quando sei preparato” + paroleSensate); In generale per utilizzare la console conviene osservare l'uso dei metodi più usati (print, println, format) in questo link. E per leggere da console? Non è così semplice come in C#..., ma dipende dalla versione di JDK utilizzata. In Java 5 (JDK1.5) si può far uso dell'oggetto scanner come nell'esempio seguente: import java.util.Scanner; public class InputExperiment { public static void main(String[] args) { String name; int age; Scanner in = new Scanner(System.in); System.out.println("inserisci il tuo nome"); // Reads a single line from the console // and stores into name variable name = in.nextLine(); System.out.println("Inserisci la tua età"); // Reads a integer from the console // and stores into age variable age=in.nextInt(); in.close(); // Prints name and age to the console System.out.println("Name :"+name); System.out.println("Age :"+age); } } In Java 6 è stato introdotto l'oggetto console, che si può utilizzare in maniera analoga alla console C#. La console di Java 6 può non funzionare in alcuni IDE (tra cui BlueJ) che simulano la console reale. Ad esempio il seguente codice import java.io.Console; public class InputExperiment2 { public static void main(String[] args) throws Exception { Console console = System.console(); if (console == null) { System.out.println("Unable to fetch console"); return; } console.printf("Per favore scrivi qualcosa\n"); String line = console.readLine(); console.printf("I saw this line: %s", line); } } In BlueJ fallisce: Nella console reale funziona: Nelle versioni di Java precedenti alla 5 bisogna scrivere del codice come riportato di seguito: import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; public class InputExperiment3 { public static void main(String[] args) { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); System.out.print("Enter String: "); try { String s = br.readLine(); System.out.print("Enter Integer: "); int i = Integer.parseInt(br.readLine()); System.out.println("Your string: " + s); System.out.println("your number: " + i); } catch(NumberFormatException nfe){ System.err.println("Invalid Format!"); } catch (Exception e) { //qualunque altra eccezione qui } } } Riflettiamo sulla classe TicketMachine e osserviamo quanto riportato in 2.12. Si può certamente fare di meglio. il metodo insertMoney() accetta solo valori non negativi il metodo printTicket() stampa il biglietto solo se il saldo supera il costo del biglietto la macchina implementa un metodo refoundBalance() che restituisce il resto. Il progetto better-ticket-machine riporta queste modifiche. É utile sapere che BlueJ evidenzia lo scope delle variabili con colori diversi così come descritto in 2.15. Per le variabili locali valgono le stesse regole di visibilità viste in C# (par 2.16). Ripassiamo i concetti di campi, parametri e variabili locali con il paragrafo 2.17. 2.4 Esercizi sulle classi Iniziamo a scrivere codice modificando la classe TicketMachine (del progetto better-ticketmachine ) in modo da svolgere gli esercizi 2.61, 2.62, 2.63. Rivediamo la classe Student del progetto lab-classes con particolare riferimento ai metodi getLoginName() e print(). Il campo Id non ha nessun metodo per modificarne il valore. Si dice in tal caso che si tratta di un campo immutabile. Ci sono oggetti che non possono essere modificati una volta creati. In tal caso si parla di oggetti immutabili. Ad esempio gli oggetti di tipo stringa sono tutti oggetti immutabili. 2.5 Per ripassare Concetti fondamentali: Classe Oggetti (o istanza di una classe) Campi (o variabili d'istanza) cosa rappresentano? Pattern di riferimento: dovrebbero essere privati (libro, pag. 26 e pag. 27) Costruttori A cosa servono? Quali regole deve soddisfare? Quanti costruttori possiamo avere? Metodi a cosa servono? Cosa si intende per segnatura? Cosa indicano le parole chiave pubblic e private? Si veda anche: http://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html Parametri parametri formali (parametri) parametri attuali (effettivi) scope di una variabile lifetime di una variabile metodi accessors (get) metodi mutators (set) Istruzioni di selezione Variabili locali 2.6 Esercizi di fine capitolo Completare la classe Book con gli esercizi 2.83 e successivi. Svolgere i due challenge exercise 2.92 e 2.93. 3 Interazione tra oggetti fig. “Un’astrazione denota le caratteristiche essenziali di un oggetto, che lo distinguono da tutti gli altri oggetti e gli forniscono nuovi contorni concettuali, dal punto di vista di chi lo guarda”.(Booch, 1998) 3.1 L'orologio digitale Iniziamo a studiare i concetti di questo capitolo con un esempio guida “l'orologio digitale”. Vogliamo realizzare un orologio digitale con le orario in formato europeo 00:00 a 23:59. Come procedere? Applicando astrazione (abstraction) e decomposizione (modularization). Esperimenti a) un orologio è fatto da cifre … (astrazione) le cifre sono quattro (decomposizione)...: come gestiamo l'interazione tra gli oggetti (cifre)? b) un orologio è fatto di due numeri rappresentati su due cifre (astrazione e decomposizione) c) un orologio è fatto da due numeri … (astrazione) il primo numero rappresenta le ore da 0 a 23 e il secondo numero rappresenta i minuti da 0 a 59 (decomposizione)...i due numeri si comportano allo stesso modo, nel senso che partono da un valore nullo e arrivano a un valore massimo per poi tornare al valore nullo...: Ogni numero è rappresentato da su due cifre decimali. Questo modo di vedere le cose sembra più semplice da gestire: creiamo la classe che rappresenta una coppia di cifre... public class NumberDisplay { private int limit; private int value; Constructor and methods omitted. } e un orologio è costituito da due oggetti NumberDisplay: public class ClockDisplay { private NumberDisplay hours; private NumberDisplay minutes; Constructor and methods omitted. } Una classe può essere pensata come un iceberg1: • Solo una piccola parte di una classe dovrebbe essere visibile all’esterno. • La maggior parte dei dettagli della classe dovrebbe essere nascosta. fig. 3.2 Class Diagram vs. Object Diagram Osserviamo il class diagram e l' object diagram nel paragrafo 3.6. 3.3 Object type e primitive type Nel paragrafo 3.6 troviamo un'altra importante differenza: quella tra variabili che contengono riferimenti a oggetti (reference data type) e variabili che contengono valori di tipi semplici (primitive data type). In un programma Java ogni oggetto ha un tipo rappresentato dalla sua classe. Ad esempio: 1 Da Mario può essere un’istanza (oggetto) della classe Pizzeria Metafora ispirata da Bertrand Meyer, Object-oriented software construction. Prentice Hall, 1988. Il Cliente Gianni può essere un’istanza (oggetto) della classe Cliente L’auto con Automobile. La frase di benvenuto può essere un’istanza (oggetto) della classe string. targa BR123XY può essere un’istanza (oggetto) della classe In un programma Java è possibile definire variabili di tipo riferimento a oggetti di una data classe. Una variabile di tipo riferimento rappresenta un riferimento, ossia un modo per individuare univocamente una specifica istanza (oggetto) di una data classe. Una variabile di tipo riferimento serve a referenziare una specifica istanza, ma non è l’istanza (oggetto). Infatti, è possibile avere più riferimenti (più variabili di tipo riferimento) a uno stesso oggetto (istanza). Il riferimento a un oggetto è ciò che Java mette a disposizione per interagire con gli oggetti. Ad esempio, mediante la variabile daMario (di tipo riferimento a Pizzeria) sarà possibile accedere allo stato dell’oggetto Da Mario e interagire con esso inviandogli messaggi (ossia invocare i suoi metodi). fig. Per ragioni pratiche, in Java c’è la possibilità di definire anche variabili che non siano riferimenti ad oggetti in senso stretto. Queste variabili (o espressioni) hanno tipo valore. Un tipo valore è messo a disposizione dal linguaggio e non è creato mediante la definizione di una classe. Il termine “tipo valore” indica che le variabili di tali tipi contengono il loro valore direttamente. I tipi riferimento e i tipi valore esauriscono le categorie di tipi disponibili in Java. La distinzione tra tipi primitivi e tipi riferimento è molto importante perché la semantica degli assegnamenti è diversa nei due casi. Una variabile di tipo primitivo può essere pensata come il nome di un contenitore che conserva un valore di quel tipo. fig. 4: esempi di variabili di tipo primitivo L’assegnamento fra tipi primitivi ha l’effetto di copiare il valore di un contenitore (membro destro) nell’altro (membro sinistro). fig. 5: L’assegnamento fra tipi primitivi L’assegnamento fra tipi riferimento ha l’effetto di cambiare il riferimento del membro sinistro verso l’oggetto referenziato dal membro destro, senza copiare valori. fig. fig. I tipi come int, bool, char, double etc. appartengono alla categoria “value type”. In Java i tipi primitivi sono: Data Type Default Value (for fields) byte 0 short 0 int 0 long 0L float 0.0f double 0.0d char '\u0000' boolean false I tipi come le string e come i tipi astratti corrispondenti al concetto di classe (come Persona, Pizzeria, Auto, etc.) appartengono alla categoria “reference type” e permettono di rappresentare gli oggetti. 3.4 Gli oggetti posso creare altri oggetti Esaminiamo il codice del progetto clock-display (par. 3.8) partendo dalla classe NumberDisplay. Gli oggetti possono creare altri oggetti (par. 3.9) usando l'operatore new(). 3.5 Overloading dei metodi e dei costruttori (Par. 3.11) In una classe possiamo avere più costruttori oppure più metodi con lo stesso nome a patto che le segnature dei metodi siano diverse: i costruttori o i metodi devono differire nella lista dei parametri formali. Questo concetto va sotto il nome di overloading. Ad esempio ClockDisplay() e ClockDisplay(int hours, int minute) hanno segnature diverse. In questo link troviamo la documentazione ufficiale Java che descrive cosa si intende per segnatura di un metodo e per overloading. 3.6 Esercizi di potenziamento Svolgere i due challenge exercises 3.31 e 3.32 (realizzare un orologio che visualizza le cifre delle ore da 1 a 12 e riporta la dicitura AM o PM). 3.7 Uso del riferimento this (Par. 3.12) apriamo il progetto mail-system. Questo progetto è, per il momento, troppo complesso per poter essere compreso completamente. Tuttavia è utile per comprendere alcuni aspetti importanti. Per prima cosa osserviamo l'utilizzo del riferimento all'oggetto corrente this nel costruttore della classe MailItem. public MailItem(String from, String to, String message) { this.from = from; this.to = to; this.message = message; } Methods omitted. } this è una parola chiave del linguaggio Java ed è il riferimento all'oggetto corrente. Serve per riferire in maniera esplicita un membro (campo o metodo) dell'oggetto corrente. L'uso più comune del riferimento this si ha quando un campo è nascosto da un parametro locale. Scorrere il codice per capire cosa fa... 3.8 Uso del debugger in BlueJ Il progetto mail-system è composto da tre classi: una molto semplice MailItem, una semplice MailClient, una un po' complessa MailServer. Creiamo un'istanza di mailserver e poi due istanze di client (juan e sophie) a cui passiamo il riferimento per il mailserver. Poi iniziamo a scambiare messaggi (mailitem) tra i due. Mettiamo un breakpoint su un'istruzione di printNextMailItem. 3.9 Per ripassare 4 Raggruppamento di oggetti In questo capitolo vedremo più in dettaglio il concetto di astrazione e di interazione di oggetti. Inizieremo con una importante applicazione del concetto di astrazione: la collezione (collection) di oggetti. Partiamo dal concetto di collezione e immaginiamo di poter scrivere una classe che rappresenti il concetto di collezione; otterremo oggetti di tipo collection in grado di memorizzare un numero arbitrario di altri oggetti. Per illustrare il concetto di collection utilizziamo l'esempio guida music-organizer. Questo progetto realizza una versione minimale di player mp3 utilizzando la libreria jlayer scaricabile dal sito http://www.javazoom.net/projects.html. 4.1 Music-organizer-v1 La versione v1 del progetto si limita a creare un oggetto di tipo ArrayList e a inserirvi un certo numero di stringhe di testo corrispondenti al “full name” (percorso del file + nome file con estensione) di file in formato .mp3. Questa versione non esegue i file musicali, ma si limita a esplorare le funzioni di base che si hanno su on oggetto di tipo ArrayList: add(), remove(index), get(index), size(). import java.util.ArrayList; public class MusicOrganizer { private ArrayList<String> files; public MusicOrganizer() { files = new ArrayList<String>(); } public void addFile(String filename) { files.add(filename); } public int getNumberOfFiles() { return files.size(); } public void listFile(int index) { if(index >= 0 && index < files.size()) { String filename = files.get(index); System.out.println(filename); } } public void removeFile(int index) { if(index >= 0 && index < files.size()) { files.remove(index); } } }//end of class Dall'esempio music-organizer-v1 si vede l'utilizzo di una particolare astrazione: la collection ArrayList. La creazione di un oggetto di tipo ArrayList deve includere anche la definizione del tipo base degli elementi della lista, dal momento che ArrayList è una classe generica. Per creare un ArrayList e assegnare il suo riferimento a una variabile si utilizza il costrutto: ArrayList<TipoBase> rifLista; //dichiarazione del riferimento rifLista = new ArrayList<TipoBase>(); oppure da Java 7 si può scrivere: rifLista = new ArrayList<>(); TipoBase è un qualsiasi tipo ottenuto mediante la definizione di una classe (deve essere un object type). Si noti che un ArrayList e in generale una collection Java non può contenere tipi primitivi (boolean, char, int, double, etc..) a meno di non ricorrere a classi wrapper. Si vedano a tal proposito i concetti di: Autoboxing e Unboxing Wrapper classes Primitive type Wrapper class boolean Boolean byte Byte char Character float Float int Integer long Long short Short double Double Number classes e java numbers Da questo esempio si è visto anche cosa succede all'indice degli elementi di un ArrayList quando si inseriscono o si rimuovono elementi. 4.2 Music-organizer-v2 Eseguire i brani musicali mediante un oggetto MusicPlayer La seconda versione del progetto utilizza la classe MusicPlayer per eseguire i file musicali caricati nell'ArrayList files. import java.util.ArrayList; public class MusicOrganizer { private ArrayList<String> files; private MusicPlayer player; public MusicOrganizer() { files = new ArrayList<String>(); player = new MusicPlayer(); } public void addFile(String filename) { files.add(filename); } //other methods public void startPlaying(int index) { String filename = files.get(index); player.startPlaying(filename); } public void stopPlaying() { player.stop(); } }//end of class MusicOrganizer La classe MusicPlayer utilizza la libreria jl di javazoom.net per creare un player di file mp3. I dettagli saranno analizzati in seguito. Ora interessa che i file sono riprodotti correttamente. 4.3 Music-organizer-v3 Processare tutti gli elementi di una collection In Java esistono diversi modi per processare tutti gli elementi di una collection. Uno dei più usati è il for-each loop (analogo al foreach di C#). ciclo predeterminato - for-each loop In Java non esiste una keyword foreach, ma si utilizza il classico for per effettuare un for-each su una collection. Cambia il modo di iterare la collection. for(ElementType element : collection) { loop body } Ad esempio nel progetto music-organizer-v3 c'é il metodo di MusicOrganizer che stampa tutti i file presenti nell'ArrayList files. In tale metodo si utilizza il for-each: /** * Show a list of all the files in the collection. */ public void listAllFiles() { for(String filename : files) { System.out.println(filename); } } Con un costrutto for-each é ovviamente possibile processare in maniera selettiva solo degli elementi della collection che soddisfano un particolare requisito come ad esempio nel metodo listMatching che stampa solo i nomi dei file che contengono il nome passato al metodo: /** * List the names of files matching the given search string. * @param searchString The string to match. */ public void listMatching(String searchString) { for(String filename : files) { if(filename.contains(searchString)) { // A match. System.out.println(filename); } } } Esercizio 4.27: aggiungere alla classe MusicOrganizer il metodo playAllSamples che esegue in sequenza un campione di tutti i brani presenti nella collection files. Attenzione: bisogna usare il metodo playAndWait e non il metodo startPlaying della classe MusicPlayer, dal momento che startPlayer ritorna non appena ha creato un nuovo thread che mette in esecuzione il brano passatogli. Il metodo palyAndWait esegue solo un campione del brano passatogli come parametro, e ritorna quando l'esecuzione è terminata. Osservazione 1: un costrutto for-each va usato quando bisogna processare tutti gli elementi della collection. Se viceversa si vuole processare gli elementi della collection fino al verificarsi di una determinata condizione è meglio ricorrere ad altri costrutti, come ad esempio il while che vedremo nel prossimo paragrafo. Osservazione2: il for-each consente di cambiare i valori ai campi degli oggetti della collection, ma non consente di eliminare o aggiungere elementi alla collection. Se si prova a fare una cosa del genere si solleva un'eccezione del tipo ConcurrentModificationException. Se si vuole avere la possibilità di aggiungere o rimuovere elementi dalla collection mentre si effettua la scansione bisogna utilizzare un ciclo while in combinazione a un iteratore, come descritto nei prossimi paragrafi. 4.4 Music-organizer-v4 Cicli indeterminati – while Esempio 1: Questo esempio mostra come iterare tutta la collection, come si sarebbe potuto fare con un for-each. Questo esempio è un ciclo sviluppato con il while, ma che è in realtà predeterminato. /** * Show a list of all the files in the collection. */ public void listAllFiles(){ int index = 0; while(index < files.size()) { String filename = files.get(index); System.out.println(filename); index++; } } Esempio 2: questo esempio mostra un ciclo indeterminato nel quale si cerca un brano che contiene nel nome una parola data in input. Questo ciclo è effettivamente un ciclo indeterminato dal momento che non è noto a priori quando si uscirà da ciclo. /** * Find the index of the first file matching the given * search string. * @param searchString The string to match. * @return The index of the first occurrence, or -1 if * no match is found. */ public int findFirst(String searchString){ int index = 0; // Record that we will be searching until a match is found. boolean searching = true; while(searching && index < files.size()) { String filename = files.get(index); if(filename.contains(searchString)) { // A match. We can stop searching. searching = false; } else { // Move on. index++; } } if(searching) { // We didn't find it. return -1; } else { // Return where it was found. return index; } } 4.5 Music-organizer-v5 Un miglioramento del progetto del MusicOrganizer: classe Track Negli esempi precedenti, le informazioni sull'artista e sul titolo del brano erano nel nome del file mp3, seguendo la convenzione “nome artista – nome brano.mp3”. Ora si vuole migliorare il progetto introducendo una classe Track che contenga i dati relativi a un brano (artista, titolo, nome completo del file) nei suoi campi e di conseguenza l'elenco dei brani gestiti dal MusicOrganizer sarà un ArrayList di oggetti di tipo Track. Per semplificare ulteriormente le cose è utile avere una classe TrackReader che ha lo scopo principale di caricare, con il metodo readTracks, i file presenti in una determinata directory e aventi estensione specificata (.mp3) in un ArrayList di Track. Il metodo readTracks assume che il nome dei file sia “nome artista – titolo brano .mp3”. Nella classe MusicOrganizer il metodo readLibrary richiama readTracks e aggiunge le tracce alla lista tracks. import java.util.ArrayList; public class MusicOrganizer{ // An ArrayList for storing music tracks. private ArrayList<Track> tracks; // A player for the music tracks. private MusicPlayer player; // A reader that can read music files and load them as tracks. private TrackReader reader; /** * Create a MusicOrganizer */ public MusicOrganizer(){ tracks = new ArrayList<Track>(); player = new MusicPlayer(); reader = new TrackReader(); readLibrary("audio"); System.out.println("Music library loaded. " + getNumberOfTracks() + " tracks."); System.out.println(); } private void readLibrary(String folderName) { ArrayList<Track> tempTracks = reader.readTracks(folderName, ".mp3"); // Put all the tracks into the organizer. for(Track track : tempTracks) { addTrack(track); } } //other methods }//end of MusicOrganizer Si noti che il metodo readLibrary di MusicOrganizer fa uso del metodo readTracks sull'oggetto reader della classe TrackReader. Questo metodo implementa il caricamento da file in oggetti di tipo Track e contiene del codice che sarà chiaro più avanti. Con questa nuova struttura della classe MusicOrganizer il metodo che stampa l'elenco delle tracce è: public void listAllTracks(){ System.out.println("Track listing: "); for(Track track : tracks) { System.out.println(track.getDetails()); } System.out.println(); } Si noti che la classe Track ha anche un metodo che stampa a console i dettagli di una traccia: public String getDetails() { return artist + ": " + title + " } L'esecuzione di un brano con la nuova struttura diventa: public void playTrack(int index){ (file: " + filename + ")"; if(indexValid(index)) { Track track = tracks.get(index); player.startPlaying(track.getFilename()); System.out.println("Now playing: " + track.getArtist() + " - " + track.getTitle()); } } 4.6 Scorrere gli elementi di una collection con un iteratore Un Iteratore è un oggetto molto potente che permette di scorrere gli elementi di una collection in maniera semplice e senza particolari restrizioni (come accadeva con il for-each). Per utilizzare un iteratore occorre importare la classe java.util.Iterator e il suo uso è associato al ciclo while nel seguente modo: Iterator<ElementType> it = myCollection.iterator(); while(it.hasNext()) { call it.next() to get the next element do something with that element } Ad esempio supponendo di fare riferimento al MusicOrganizer si potrebbe scrivere il metodo /** * List all the tracks. */ public void listAllTracks(){ Iterator<Track> it = tracks.iterator(); while(it.hasNext()) { Track t = it.next(); System.out.println(t.getDetails()); } } Con un oggetto di tipo iteratore è possibile anche eliminare o aggiungere elementi a una collection. Ad esempio si potrebbe scrivere un frammento di codice come il seguente: Iterator<Track> it = tracks.iterator(); while(it.hasNext()) { Track t = it.next(); String artist = t.getArtist(); if(artist.equals(artistToRemove)) { it.remove(); } } Si noti che la rimozione di un elemento dalla collection avviene utilizzando direttamente il riferimento dato dall'iteratore. Per approfondimenti si veda anche questo link. 4.7 Modalità di iterazione in Java (riassunto) ciclo for semplice (con indice), come quello visto in C# ciclo for-each ciclo while con indice iteratore con ciclo while ciclo do while con indice o con iteratore 4.8 Esercizi con le classi Esercizi 4.40, 4.41, 4.42 Osservazioni importanti: la classe ClubDemo utilizza oggetti anonimi nel metodo join, ossia oggetti che non hanno un reference, e che sono creati quando servono. public void demo() { club.join(new Membership("David", 2, 2004)); club.join(new Membership("Michael", 1, 2004)); System.out.println("The club has " + club.numberOfMembers() + " members."); } Progetto Music-Organizer-v5e (con shuffle) Esercizi 4.43, 4.44. 4.45 Riferimenti utili: http://docs.oracle.com/javase/7/docs/api/java/util/Random.html http://docs.oracle.com/javase/7/docs/api/java/util/Random.html#nextInt(int) Versione1: Fare una prima versione che genera un numero a caso tra 0 e tracks.size()-1 e stampa a video i titoli delle tracce selezionate. Poi passare alla versione che utilizza il metodo playSample per ascoltare il risultato. Suggerimento: partendo dal metodo listAllFiles del §4.4 si arriva facilmente a un metodo come printRandomSamples import java.util.Random; // public void printRandomSamples(){ Random randomGen = new Random(); int index = 0; while(index < tracks.size()) { int posizione = randomGen.nextInt(tracks.size()); String filename =tracks.get(posizione).getFilename(); System.out.println(filename); index++; } } /** * NOTA IMPORTANTE: una volta lanciata la sequenza non è possibile interromperla! */ public void playRandom(){ Random randomGen = new Random(); int index = 0; try{ while(index < tracks.size()) { player.playSample(tracks.get(randomGen.nextInt(tracks.size())).getFilename()); index++; } } catch (Exception e) { e.printStackTrace(); } } Per ottenere un metodo che esegue una sequenza di brani e che possa essere interrotto occorre modificare la classe MusicPlayer come segue: public class MusicPlayer { // The current player. It might be null. private AdvancedPlayer player; //impostata a true quando si deve eseguire una lista di brani //impostata a false quando si deve interrompere una lista di brani private boolean vaiAvanti; //altri metodi public void playListSamples(final ArrayList<String> filenames) { try { //vaiAvanti è true fino a quando non si decide di interrompere la sequenza vaiAvanti = true; Thread playerThread = new Thread() { public void run() { Iterator<String> it = filenames.iterator(); while(it.hasNext() && vaiAvanti){ String brano = it.next(); try { setupPlayer(brano); player.play(500); } catch(JavaLayerException e) { reportProblem(brano); } catch (Exception ex) { ex.printStackTrace(); } finally { killPlayer(); } } } }; playerThread.start(); } catch (Exception ex) { ex.printStackTrace(); } } //altri metodi public void stopList() { vaiAvanti = false; killPlayer(); } A questo punto nella classe MusicOrganizer si possono aggiungere i seguenti metodi: * * * * * */ /** Esegue l'elenco dei file delle tracce, scelti a caso. Il numero di file eseguiti è uguale alla dimensione della lista. E' possibile che alcuni file siano eseguiti più volte e che alcuni file non siano eseguiti NOTA IMPORTANTE: questo metodo può essere interrotto con il metodo stopList public void playRandomSamples(){ Random randomGen = new Random(); ArrayList<String> shuffled = new ArrayList<String>(); int index = 0; try{ while(index < tracks.size()) { //calcolo indice random int posizione = randomGen.nextInt(tracks.size()); shuffled.add(tracks.get(posizione).getFilename()); index++; } player.playListSamples(shuffled); } } catch (Exception e) { e.printStackTrace(); } public void stopPlayingList() { player.stopList(); } Versione 2: Fare una seconda versione che esegue in modalità random i brani della lista una sola volta e poi ferma l'esecuzione. Suggerimento: creare una copia della lista delle tracce (come?) e poi estrarre a caso un indice dalla lista e eseguire il relativo brano. Il brano eseguito sarà rimosso dalla copia della lista. Quando la copia della lista sarà vuota ogni brano sarà stato eseguito una sola volta a caso. /** * Stampa i brani presenti nella lista in ordine casuale. Ogni brano è eseguito una sola volta */ public void printRandomOnceSamples(){ //shallow copy mediante copy constructor ArrayList<Track> copiedList= new ArrayList<Track>(tracks); Random randomGen = new Random(); ArrayList<String> shuffled = new ArrayList<String>(); try{ while(copiedList.size()>0) { //calcolo indice random int posizione = randomGen.nextInt(copiedList.size()); shuffled.add(copiedList.get(posizione).getFilename()); copiedList.remove(posizione); } for(String brano: shuffled){ // player.playListSamples(shuffled); System.out.println(brano); } } catch (Exception e) { e.printStackTrace(); } } /** * Esegue i brani presenti nella lista in ordine casuale. Ogni brano è eseguito una sola volta */ public void playRandomOnceSamples(){ ArrayList<Track> copiedList= new ArrayList<Track>(); //copio la lista delle tracce in una lista temporanea copiedList <-- tracks listDeepCopy(copiedList, tracks); Random randomGen = new Random(); ArrayList<String> shuffled = new ArrayList<String>(); try{ while(copiedList.size()>0) { //calcolo indice random int posizione = randomGen.nextInt(copiedList.size()); shuffled.add(copiedList.get(posizione).getFilename()); copiedList.remove(posizione); } player.playListSamples(shuffled); } catch (Exception e) { e.printStackTrace(); } } Copia di un ArrayList in un altro ArrayList Per copiare una lista in un'altra lista ci sono diversi metodi, ma la cosa veramente importante è comprendere cosa si vuole copiare! La cosa più semplice è fare una shallow copy di una lista, vale a dire una nuova lista che contiene i riferimenti agli stessi oggetti della prima. Se si modifica un oggetto nella prima lista lo si modifica anche nella seconda. Solo nel caso di tipi primitivi si ottengono due liste di elementi disgiunti. Per fare una shallow copy si può ricorrere al copy constructor: ArrayList<Track> copiedList= new ArrayList<Track>(tracks); oppure si può fare un metodo che esegue la copia elemento per elemento private void listCopy(ArrayList<Track> dest, ArrayList<Track> source){ try{ dest.clear(); for(int i=0; i<source.size(); i++){ dest.add(source.get(i)); } } catch (Exception e){ e.printStackTrace(); } } il risultato sarà lo stesso. Anche nel secondo caso otterremo una shallow copy dal momento che l'istruzione composta dest.add(source.get(i)); utilizza il riferimento della lista sorgente per inserire un nuovo elemento nella lista destinazione. Ancora sul progetto Club Esercizi 4.54 e 4.55 Progetto Products (StockManager, Product, etc.) Esercizi 4.56, 4.57, 4.58, 4.59, 4.60 Suggerimento: scrivere nell'ordine printProductDetails, findProduct, numberInStock, delivery, 4.9 Collections a dimensione fissa: gli array Le collections a dimensione fissa sono gli array. Rispetto alle collezioni a dimensione variabile, gli array hanno caratteristiche peculiari che li rendono particolarmente adatti in certi contesti. In particolare gli array hanno lo svantaggio di non poter variare la loro dimensione una volta creati, tuttavia rispetto alle collections a dimensione variabile hanno i seguenti vantaggi: L'accesso agli elementi di un array è in genere più efficiente (veloce) dell'accesso agli elementi di una collections a dimensione variabile. Gli array possono memorizzare sia oggetti (riferimenti a oggetti) sia tipi primitivi. Le collections a dimensione variabile possono memorizzare solo oggetti2. Un'altra caratteristica fondamentale degli array è che essi supportano una sintassi speciale per l'accesso ai singoli elementi. Alcuni esempi Per dichiarare un array: int hour; // A single int variable. int[] hourCounts; // An int-array variable. Creazione di oggetti di tipo array: hourCounts = new int[24]; Definizione (dichiarazione e creazione di oggetto) di un array: String[] names = new String[10]; Un elemento di un array si comporta come una variabile del suo tipo base: hourCounts[hour]++; hourCounts[hour] = hourCounts[hour] + 1; hourCounts[hour] += 1; L'iterazione su un array si può fare con un classico ciclo for, come nel seguente esempio: for(int hour = 0; hour < hourCounts.length; hour++) { System.out.println(hour + ": " + hourCounts[hour]); } oppure con un ciclo while int hour = 0; while(hour < hourCounts.length) { System.out.println(hour + ": " + hourCounts[hour]); hour++; } Sebbene sia possibile usare un ciclo for-each o un iteratore su un array in genere ciò è sconsigliato, perché il for-each non fornisce la variabile indice che serve per accedere agli elementi di un array, mentre l'iteratore costringe a usare una sintassi poco naturale nel caso di array. 4.10 Passaggio dei parameteri in java http://docs.oracle.com/javase/tutorial/java/javaOO/arguments.html Passing Primitive Data Type Arguments Primitive arguments, such as an int or a double, are passed into methods by value. This means that any changes to the values of the parameters exist only within the scope of the method. When the method returns, the parameters are gone and any changes to them are lost. Here is an example: public class PassPrimitiveByValue { public static void main(String[] args) { 2 In effetti è possibile inserire oggetti che simulano valori primitivi mediante il meccanismo dell'autoboxing. Si veda questo link per i dettagli. int x = 3; // invoke passMethod() with // x as argument passMethod(x); // print x to see if its // value has changed System.out.println("After invoking passMethod, x = " + x); } // change parameter in passMethod() public static void passMethod(int p) { p = 10; } } When you run this program, the output is: After invoking passMethod, x = 3 Passing Reference Data Type Arguments Reference data type parameters, such as objects, are also passed into methods by value. This means that when the method returns, the passed-in reference still references the same object as before. However, the values of the object's fields can be changed in the method, if they have the proper access level. For example, consider a method in an arbitrary class that moves Circle objects: public void moveCircle(Circle // code to move origin of circle.setX(circle.getX() circle.setY(circle.getY() } circle, int deltaX, int deltaY) { circle to x+deltaX, y+deltaY + deltaX); + deltaY); // code to assign a new reference to circle circle = new Circle(0, 0); Let the method be invoked with these arguments: moveCircle(myCircle, 23, 56) Inside the method, circle initially refers to myCircle. The method changes the x and y coordinates of the object that circle references (i.e., myCircle) by 23 and 56, respectively. These changes will persist when the method returns. Then circle is assigned a reference to a new Circle object with x = y = 0. This reassignment has no permanence, however, because the reference was passed in by value and cannot change. Within the method, the object pointed to by circle has changed, but, when the method returns, myCircle still references the same Circle object as before the method was called. Altri esempi che spiegano il funzionamento del passaggio dei parametri si trovano in questo link: http://zitogiuseppe.com/jsem/parametri.html 4.11 Per ripassare (Par. 5.1) Iniziamo con l'esempio tech-support-complete per vedere cosa si può fare con oggetti di tipo HashMap e HashSet. 5.2 Un risponditore banale: tech-support1 (Par. 5.2) Analizziamo la versione tech-support1 che comprende la versione di base del progetto e implementa un risponditore automatico che risponde sempre con la stessa frase a ogni parola chiave. 5.3 La documentazione Java Apriamo la documentazione ufficiale Java su link Oracle (da BlueJ ) e in locale. Da BlueJ su può aprire la documentazione selezionando Help>Java Class Libraries. Come esercizio troviamo la documentazione per la classe java.lang.String. Osserviamo la configurazione di BlueJ relativa alla documentazione. Esercizi: Exercise 5.3 Look up the startsWith method in the documentation for String. There are two versions. Describe in your own words what they do and the differences between them. Exercise 5.4 Is there a method in the String class that tests whether a string ends with a given suffix? If so, what is it called and what are its parameters and return type? endsWith() Exercise 5.5 Is there a method in the String class that returns the number of characters in the string? If so, what is it called and what are its parameters? length() Exercise 5.6 If you found methods for the two tasks above, how did you find them? Is it easy or hard to find methods you are looking for? Why? Concetto di interfaccia e di implementazione di una classe L'interfaccia di una classe rappresenta cosa la classe fa e come può essere utilizzata dall'esterno. L'interfaccia di un metodo rappresenta la segnatura del metodo con l'aggiunta del tipo di ritorno. L'interfaccia di una classe è di norma descritta nella documentazione. L'implementazione di una classe è data dal codice della classe. 5.4 Utilizzo delle funzioni di libreria Gli oggetti di tipo stringa sono oggetti immutabili. Ciò significa che una volta creati non possono più essere modificati. I metodi della classe String che servono per manipolare stringhe restituiscono stringhe modificate che sono nuove stringhe, diverse da quelle di partenza. Ad esempio il metodo toUpperCase() applicato a un oggetto di tipo stringa non modifica l'oggetto su cui è applicato, ma restituisce una nuova stringa. Le istruzioni: String prova = “ciao Mondo”; → prova = new String(“ciao mondo”); String prova2= prova.toUpperCase(); I metodi che restituiscono oggetti possono essere concatenati per ottenere l'esecuzione di più metodi in sequenza. Ad esempio il metodo getInput() della classe reader dell'esempio tech- support1 restituisce un oggetto di tipo stringa. Possiamo fare in modo da applicare la funzione trim() e la funzione toLowerCase() per rendere il programma insensibile agli spazi e alle variazioni di case. Nel codice del metodo start() possiamo scrivere: while(!finished) { String input = reader.getInput().trim().toLowerCase(); if(input.equals("bye")) { finished = true; //etc... Esercizi 5.8 e 5.9 L'uguaglianza tra stringhe non va mai verificata con l'operatore ==. Infatti in Java l'operatore == verifica se i due operandi ( a sinistra e destra) sono lo stesso oggetto e non se hanno lo stesso valore. Per verificare l'uguaglianza tra oggetti di tipo stringa bisogna usare il metodo equals() che verifica se il contenuto delle due stringhe è uguale. Ad esempio: The following example shows the usage of java.lang.String.equals() method. package com.tutorialspoint; import java.lang.*; public class StringDemo { public static void main(String[] args) { String str1 = "sachin tendulkar"; String str2 = "amrood admin"; String str3 = "amrood admin"; // checking for equality boolean retval1 = str2.equals(str1); boolean retval2 = str2.equals(str3); // prints the return value System.out.println("str2 is equal to str1 = " + retval1); System.out.println("str2 is equal to str3 = " + retval2); } } Let us compile and run the above program, this will produce the following result: str2 is equal to str1 = false str2 is equal to str3 = true Esercizi 5.10 e 5.11 La classe java.util.Random Esercizio 5.14 – creare la classe RandomTester. Attenzione al fatto che quando si utilizza un oggetto della classe Random bisognerebbe utilizzare una singola istanza della classe Random (creata nel costruttore di RandomTester) e memorizzarla in un campo. Ogni volta che si vuole un nuovo valore casuale si dovrebbe invocare il metodo nextQualcosa() e non ricreare l'oggetto Random. Esercizi 5.16, 5.17, 5.18, 5.20 Un generatore di risposte casuale – verso tech-support2: sviluppo del codice a partire dal progetto tech-support1 (par. 5.4.3) 5.5 Utilizzo di package, import e archivi jar Per utilizzare le classi bisogna istruire il compilatore sulle classi che si vogliono utilizzare tramite la direttiva import. Siccome le classi delle librerie Java sono diverse migliaia, per organizzarle in maniera ordinata si utilizzano i package. I package sono gruppi di classi logicamente correlate. Per importare le classe all'interno di un package bisogna scrivere import package-name.Class-name nel file della classe che utilizza le classi della libreria. I package possono essere organizzati in sub-package, come ad esempio java.util. È possibile richiamare tutte le classi di un package scrivendo import package-name.*, ma nel nostro corso preferiamo scrivere esplicitamente le classi che sono utilizzate per dare maggiore chiarezza al codice. Le classi del package java.lang sono automaticamente importate, infatti per usare oggetti di tipo String non è necessario scrivere import java.lang.String. Organizzazione delle classi in package To make types easier to find and use, to avoid naming conflicts, and to control access, programmers bundle groups of related types into packages. Definition: A package is a grouping of related types providing access protection and name space management. Note that types refers to classes, interfaces, enumerations, and annotation types. Enumerations and annotation types are special kinds of classes and interfaces, respectively, so types are often referred to in this lesson simply as classes and interfaces. The types that are part of the Java platform are members of various packages that bundle classes by function: fundamental classes are in java.lang, classes for reading and writing (input and output) are in java.io, and so on. You can put your types in packages too. La creazione dei package in BlueJ è molto semplice: http://people.cis.ksu.edu/~schmidt/300s05/bluej.packages.html Say that you want to use BlueJ to construct a folder, Assign1, that contains a package named P. Within package P you wish to insert a class, C.java. Here is what you do: 1.Click on the Project menu, and click on its New Project menu item. A file dialog appears that asks you the name of the folder you wish to create---type Assign1 and press the create button. Important: You can tell BlueJ where on your disk you wish to create the folder, Assign1. Remember where you created it --- this will make your life easier when it is time to submit your work for grading. 2.As a result of the previous step, BlueJ creates a new folder named Assign1, and it opens a new window presenting the folder to you. (Alas, in BlueJ, a folder (directory) is called a ``project''!) Now, we are ready to create the package, P: Within the Assign1 window, click on the Edit menu, and click on its New Package menu item. A dialog appears and asks you for the name of the package---type P and press OK. 3.As a result of the previous step, a folder (package) named P has been created inside folder Assign1. 4.Now you are ready to include class C within package P. You can do this two ways: 1. Write it from scratch: double-click on folder P; this opens it and displays a new window. within the window for P, click on the New Class button. A dialog appears and asks for the name of the class---type C and press OK. The previous step creates a file, C.java, inside folder P. When you open C.java, you will find that BlueJ has constructed an empty class that looks like this: package P; public class C { ... } At this point, you can edit and compile the various java-files as usual. 2. Copy it from somewhere else: Say that class C is already written and saved as the file, C.java, in another folder. You can copy it into the package by double-click on folder P; this opens the package and displays a new window. click on the Edit menu and then click on the Add Class from File menu item. A file dialog appears; use this to locate C.java; once you have located it, press the Add button. On newer releases of BlueJ, you can copy the contents of an entire package all at once: Say that you want to copy a package Q in its entirety into Assign1: 1.Open the window for Assign1. 2.Select Edit and then New Package. Type the name, Q, into the dialog. 3.Open the window for Q; select Project and then Import. Use the file dialog to locate the folder (package) named Q on your file system. BlueJ will then copy all the classes in folder Q into the BlueJ package Q you just constructed. Per gli approfondimenti sui packages si veda http://docs.oracle.com/javase/tutorial/java/package/packages.html Creazione di archivi jar I file jar sono archivi complessi contenenti classi java, solitamente compilate. Il formato jar è il formato standard di distribuzioni delle api java. Ad esempio nella cartella della JDK andando in Program Files\Java\jdk_numero_ver\jre\lib si trovano diversi file jar e tra questi il file rt.jar che contiene le principali classi fornite nel java runtime environment. In BlueJ è molto semplice creare un file jar del proprio progetto, basta andare in Project>Create Jar Files e qui si apre il wizard che porta alla creazione del jar. Per gli approfondimenti sugli archivi jar si rinvia al link http://docs.oracle.com/javase/tutorial/deployment/jar/ 5.6 Uso di mappe per creare associazioni Vogliamo creare un risponditore automatico che associ a ogni parola chiave una risposta plausibile. Se il testo inserito dall'utente contiene una delle parole chiave gestite dal risponditore, la risposta automatica sarà la frase associata alla parola chiave. Per fare questo tipo di risponditore automatico utilizzeremo la classe HashMap, che è una particolare Map. Esercizi 5.23 e 5.24: Cercare nella documentazione Java la classe HashMap e l'interfaccia Map http://docs.oracle.com/javase/6/docs/api/java/util/HashMap.html http://docs.oracle.com/javase/6/docs/api/java/util/Map.html Concetto di Map: la classe HashMap Una mappa è una collection che memorizza coppie key/value. I valori possono essere ricercati per parola chiave. Una prima differenza tra un ArrayList e una Map è che ogni entry di una Map è una coppia di oggetti e non un singolo oggetto. Una seconda differenza tra un ArrayList e una Map è che gli elementi di una Map non si selezionano con un indice, ma specificando una chiave. Un elenco telefonico è un esempio di Map: le chiavi sono i nomi degli utenti e i valori sono i rispettivi numeri di telefono. Le Map funzionano bene quando si conosce la chiave e si vuole conoscere il valore associato (operazione nota come lookup). L'operazione inversa (reverse lookup) è possibile, ma molto meno efficiente. La classe HashMap è una classe che implementa l'interfaccia Map. I metodi più importanti della classe HashMap sono put e get. Il metodo put inserisce una coppia key/value nella mappa, mentre il metodo get restituisce il valore associato a una chiave. HashMap<String, String> phoneBook = new HashMap<String, String>(); //in Java 7 si può anche scrivere //HashMap<String, String> phoneBook = new HashMap<>(); phoneBook.put("Charles Nguyen", "(531) 9392 4587"); phoneBook.put("Lisa Jones", "(402) 4536 4674"); phoneBook.put("William H. Smith", "(998) 5488 0123"); Ricercare un numero di telefono dal phoneBook per una data persona è semplice: String number = phoneBook.get("Lisa Jones"); System.out.println(number); Esercizio 5.25: metodo size(); Esercizio 5.25: sviluppare la classe MapTester. Esercizi da 5.27 a 5.32 Come effettuare un ciclo sugli elementi di una Mappa? Esistono diversi modi: ciclo for-each: http://stackoverflow.com/questions/1066589/java-iterate-through-hashmap If you're only interested in the keys, you can iterate through the keySet() of the map: Map<String, Object> map = ...; for (String key : map.keySet()) { // ... } If you only need the values, use values() : for (Object value : map.values()) { // ... } Finally, if you want both the key and value, use entrySet() : for (Map.Entry<String, Object> entry : map.entrySet()) { String key = entry.getKey(); Object value = entry.getValue(); // ... } One caveat: if you want to remove items mid-iteration, you'll need to do so via an Iterator http://stackoverflow.com/questions/1066589/java-iterate-throughhashmap/1066603#1066603 Iterate through the entrySet like so: public static void printMap(Map mp) { Iterator it = mp.entrySet().iterator(); while (it.hasNext()) { Map.Entry pairs = (Map.Entry)it.next(); System.out.println(pairs.getKey() + " = " + pairs.getValue()); it.remove(); // avoids a ConcurrentModificationException } } Ad esempio per iterare un HashMap<String, String> phoneBook si potrebbe usare un ciclo for-each come nel seguente esempio: import java.util.HashMap; import java.util.Map; /** *MapTester implementa un elenco telefonico mediante una classe HashMap. * * @author Prof. Malafronte * @version 0.1 */ public class MapTester { HashMap<String, String> phoneBook; /** * Crea un un elenco telefonico */ MapTester(){ phoneBook = new HashMap<String,String>(); } /** * Inserisce un numero di telefono nel phoneBook * @param name rappresenta il nome dell'elenco * @param number rappresenta il numero di telefono */ public void enterNumber(String name, String number){ phoneBook.put(name , number); } /** * @param name il nome da ricercare nell'elenco * @return il numero di telefono corrispondente a name */ public String lookupNumber(String name){ return phoneBook.get(name); } /** * Stampa l'elenco dei nomi e dei numeri di telefono */ public void printMap(){ for (Map.Entry<String, String> entry : phoneBook.entrySet()) { String key = entry.getKey(); String value = entry.getValue(); System.out.println("nome = " + key + " numero = " + value); // ... } } /** * Stampa l'elenco dei nomi */ public void printMapKey(){ for ( String key : phoneBook.keySet()) { System.out.println("nome = " + key); // ... } } /** * Stampa l'elenco dei numeri di telefono */ public void printMapValues(){ for ( String value : phoneBook.values()) { System.out.println("numero = " + value); // ... } } } Esercizio: Modificare la classe Responder del progetto tech-support1 in modo da utilizzare un HashMap per contenere le associazioni tra parole chiave e risposte. Questa versione funziona qualora l'utente scrive una parola corrispondente a una chiave. Come fare una versione del progetto che funziona anche quando l'utente scrive un'intera frase contenente una parola chiave? Concettualmente dobbiamo trasformare la stringa inserita dall'utente in un insieme di parole, e poi dobbiamo controllare se almeno una di queste parole corrisponde ad una parola chiave. Tutto ciò sarà più semplice usando il concetto di Set e i metodi predefiniti della classe String. Concetto di Set: classe HashSet Cosa rappresenta un Set? La documentazione standard afferma che: “A collection that contains no duplicate elements. More formally, sets contain no pair of elements e1 and e2 such that e1.equals(e2), and at most one null element. As implied by its name, this interface models the mathematical set abstraction.” La libreria Java standard fornisce diverse varianti di Set, quella che analizzeremo qui è quella chiamata HashSet. Così come ArrayList è una particolare List, anche HashMap è una particolare Map. Qualche esempio di codice: import java.util.HashSet; ... HashSet<String> mySet = new HashSet<String>(); mySet.add("one"); mySet.add("two"); mySet.add("three"); Per iterare su un HashSet for(String item : mySet) { do something with that item } In un Set non c'è uno specifico ordine tra gli elementi e inserire un elemento più volte non ha nessun effetto sul Set. Suddivisione di stringhe Il metodo getInput di InputReader può essere modificato in modo da restituire un HashSet di parole anziché una stringa private Scanner reader; public InputReader() { reader = new Scanner(System.in); } public HashSet<String> getInput() { System.out.print("> "); // print prompt String inputLine = reader.nextLine().trim().toLowerCase(); String[] wordArray = inputLine.split(" "); // add words from array into hashset HashSet<String> words = new HashSet<String>(); for(String word : wordArray) { words.add(word); } return words; } Il metodo per dividere le stringhe in sotto-stringhe delimitate da un carattere è il metodo split(), che in realtà accetta espressioni ben più complesse di un semplice delimitatore. Si noti che l'utilizzo di un HashSet asicura che non ci siano duplicazioni nelle parole inserite nell'insieme. La creazione di una collection di tipo HashSet potrebbe essere fatta utilizzando la classe Arrays e il metodo statico asList anziché utilizzare il for-each: HashSet<String> words = new HashSet<String>(Arrays.asList(wordArray)); Rivediamo il progetto tech-support-complete. Esercizi Esercizio: creare la classe MyStack a partire dalla classe ArrayList. Implementare tutti i metodi della classe Stack come descritti nella documentazione Java: http://docs.oracle.com/javase/7/docs/api/java/util/Stack.html La classe avrà come tipo base Track vista precedentemente. In aggiunta ai metodi definiti nella documentazione Java la classe Mystack avrà anche un metodo shallowCopy e un metodo deepCopy. //restituisce una copia dell'oggetto MyStack copiando campo per campo public MyStack shallowCopy() //restituisce una copia dell'oggetto ricreando gli oggetti di cui è composto lo stack public MyStack deepCopy() Esercizio: implementare la stessa classe dell'esercizio precedente, utilizzando un array come tipo base per lo stack di Track. Per approfondimenti sulla copia (cloning di oggetti) si veda il link: http://howtodoinjava.com/2012/11/08/a-guide-to-object-cloning-in-java/ Esercizio: Creare la classe MyQueue usando un array per memorizzare gli elementi della coda. Gli elementi base siano oggetti della classe Track. Ipotizzare di avere un array gestito come buffer circolare: i puntatori al primo e all'ultimo elemento, rispettivamente head e bottom dovranno essere incrementati in aritmetica modulo N dove N è la dimensione dell'array. Implementare i metodi dell'interfaccia Queue come descritti nella documentazione Java: http://docs.oracle.com/javase/7/docs/api/java/util/Queue.html Esercizio: realizzare la classe Complesso che rappresenta un numero complesso. In particolare realizzare i metodi che permettono di effettuare il prodotto, la somma, la sottrazione tra numeri complessi e la conversione da coordinate cartesiane a polari e viceversa. Esercizio: realizzare la classe Matrice che rappresenta le matrici di numeri interi NxM. Realizzare i metodi: somma, differenza, prodotto righe per colonne, somma di tutti gli elementi, trasposta, matrice ordinata (una nuova matrice con gli elementi disposti in ordine di riga). Modificare la classe matrice in modo che gli elementi della matrice siano numeri complessi 5.7 La documentazione delle classi Uso di javadoc In BlueJ si può richiamare Javadoc per generare automaticamente la documentazione a partire dal codice selezionando Tools>Project Documentation. Le regole per scrivere documentazione con javadoc sono semplici: I commenti per javadoc sono espressi così: /** This is a javadoc comment. */ Such a comment immediately preceding the class declaration is read as a class comment. If the comment is directly above a method signature, it is considered a method comment. In Java, using javadoc, several special key symbols are available for formatting the documentation. These key symbols start with the @ symbol and include @version @author @param @return Tutti i dettagli su javadoc si possono trovare nella documentazione ufficiale Java: http://www.oracle.com/technetwork/java/javase/documentation/index-137868.html http://stackoverflow.com/questions/8493352/how-to-make-documentation-with-netbeans-andjavadoc http://docs.oracle.com/javase/6/docs/technotes/tools/windows/javadoc.html Ad esempio: A doc comment is written in HTML and must precede a class, field, constructor or method declaration. It is made up of two parts -- a description followed by block tags. In this example, the block tags are , , and . /** * Returns an Image object that can then be painted on the screen. * The url argument must specify an absolute {@link URL}. The name * argument is a specifier that is relative to the url argument. * <p> * This method always returns immediately, whether or not the * image exists. When this applet attempts to draw the image on * the screen, the data will be loaded. The graphics primitives * that draw the image will incrementally paint on the screen. * * @param url an absolute URL giving the base location of the image * @param name the location of the image, relative to the url argument * @return the image at the specified URL * @see Image */ public Image getImage(URL url, String name) { try { return getImage(new URL(url, name)); } catch (MalformedURLException e) { return null; } } Notes: The resulting HTML from running Javadoc is shown below Each line above is indented to align with the code below the comment. The first line contains the begin-comment delimiter ( ). Starting with Javadoc 1.4, the leading asterisks are optional. Write the first sentence as a short summary of the method, as Javadoc automatically places it in the method summary table (and index). Notice the inline tag , which converts to an HTML hyperlink pointing to the documentation for the URL class. This inline tag can be used anywhere that a comment can be written, such as in the text following block tags. If you have more than one paragraph in the doc comment, separate the paragraphs with a paragraph tag, as shown. Insert a blank comment line between the description and the list of tags, as shown. The first line that begins with an "@" character ends the description. There is only one description block per doc comment; you cannot continue the description following block tags. The last line contains the end-comment delimiter ( ) Note that unlike the begin-comment delimiter, the end-comment contains only a single asterisk. For more examples, see Simple Examples. So lines won't wrap, limit any doc-comment lines to 80 characters. Here is what the previous example would look like after running the Javadoc tool: getImage public Image getImage(URL url, String name) Returns an object that can then be painted on the screen. The argument must specify an absolute URL. The argument is a specifier that is relative to the argument. This method always returns immediately, whether or not the image exists. When this applet attempts to draw the image on the screen, the data will be loaded. The graphics primitives that draw the image will incrementally paint on the screen. Parameters: - an absolute URL giving the base location of the image. - the location of the image, relative to the argument. Returns: the image at the specified URL. See Also: Public vs. Private3 Fields, constructors, and methods can all be either public or private, although so far we have seen mostly private fields and public constructors and methods. We shall come back to this below. Access modifiers define the visibility of a field, constructor, or method. If a method, for example, is public, it can be invoked from within the same class or from any other class. Private methods, on the other hand, can be invoked only from within the class in which they are declared. They are not visible to other classes. 3 Per i dettagli sui modificatori di accesso si veda http://docs.oracle.com/javase/tutorial/java/javaOO/accesscontrol.html The interface of a class is the set of details that another programmer using the class needs to see. It provides information about how to use the class. The interface includes constructor and method signatures and comments. It is also referred to as the public part of a class. Its purpose is to define what the class does. The implementation is the section of a class that defines precisely how the class works. The method bodies, containing the Java statements, and most fields are part of the implementation. The implementation is also referred to as the private part of a class. The user of a class does not need to know about the implementation. In fact, there are good reasons why a user should be prevented from knowing about the implementation (or at least from making use of this knowledge). This principle is called information hiding. The public keyword declares an element of a class (a field or method) to be part of the interface (i.e., publicly visible); the private keyword declares it to be part of the implementation (i.e., hidden from outside access). Information Hiding A programmer making use of a class should not need to know the internals; second, a user should not be allowed to know the internals. A class should not “know” (depend on) the internal details of another class. The programmer of both classes might even be the same person, but the classes should still be loosely coupled. It is important to understand that the private keyword enforces information hiding by not allowing other classes access to this part of the class. This ensures loose coupling and makes an application more modular and easier to maintain. Regola generale: I campi dovrebbero essere sempre privati per non esporre all'esterno la propria struttura. Alcuni metodi di una classe possono essere privati, quando hanno senso solo come metodi di servizio all'interno di una classe. Ad esempio metodi che rappresentano blocchi di codice che si ripetono e sono utilizzati da altri metodi. La parola chiave static The keyword static is Java’s syntax to define class variables. Class variables are fields that are stored in a class itself, not in an object. This makes them fundamentally different from instance variables (the fields we have dealt with so far). Exactly one copy exists of a class variable at all times, independent of the number of created instances. Ad esempio: http://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html public class Bicycle { private int cadence; private int gear; private int speed; // add an instance variable for the object ID private int id; // add a class variable for the // number of Bicycle objects instantiated private static int numberOfBicycles = 0; ... } Class variables are referenced by the class name itself, as in Bicycle.numberOfBicycles This makes it clear that they are class variables. Le costanti – static final http://docs.oracle.com/javase/tutorial/java/javaOO/classvars.html The static modifier, in combination with the final modifier, is also used to define constants. The final modifier indicates that the value of this field cannot change. For example, the following variable declaration defines a constant named PI, whose value is an approximation of pi (the ratio of the circumference of a circle to its diameter): static final double PI = 3.141592653589793; Constants defined in this way cannot be reassigned, and it is a compile-time error if your program tries to do so. By convention, the names of constant values are spelled in uppercase letters. If the name is composed of more than one word, the words are separated by an underscore (_). 5.8 Per ripassare Jar file 6 Il progetto delle classi (cenni) - Design gudelines Coupling (the lower is better) The term coupling refers to the interconnectedness of classes. We have already discussed in earlier chapters that we aim to design our applications as a set of cooperating classes that communicate via well-defined interfaces. The degree of coupling indicates how tightly these classes are connected. We strive for a low degree of coupling, or loose coupling. The degree of coupling determines how hard it is to make changes in an application. In a tightly coupled class structure, a change in one class can make it necessary to change several other classes as well. This is what we try to avoid, because the effect of making one small change can quickly ripple through a complete application. In addition, finding all the places where changes are necessary and actually making the changes can be difficult and time consuming. Cohesion (the higher is better) The term cohesion relates to the number and diversity of tasks for which a single unit of an application is responsible. Cohesion is relevant for units of a single class and an individual method. Ideally, one unit of code should be responsible for one cohesive task (that is, one task that can be seen as a logical unit). A method should implement one logical operation, and a class should represent one type of entity. The main reason behind the principle of cohesion is reuse: if a method or a class is responsible for only one well-defined thing, then it is much more likely that it can be used again in a different context. A complementary advantage of following this principle is that, when change is required to some aspect of an application, we are likely to find all the relevant pieces located in the same unit. Approfondimenti: Patterns in Practice: Cohesion and Coupling by Jeremy Miller http://en.wikipedia.org/wiki/Coupling_(computer_programming) http://en.wikipedia.org/wiki/Cohesion_(computer_science) 6.2 Duplicazione del codice (da evitare) Code duplication is an indicator of bad design. The problem with code duplication is that any change to one version must also be made to another if we are to avoid inconsistency. This increases the amount of work a maintenance programmer has to do, and it introduces the danger of bugs. Per evitare la duplicazione del codice basta racchiudere il codice utilizzato più volte in metodi e/o classi che sono poi richiamate quando serve. 6.3 Incapsulamento e dipendenza (information hiding enforces decoupling) Proper encapsulation in classes reduces coupling and thus leads to a better design. The encapsulation guideline (hiding implementation information from view) suggests that only information about what a class can do should be visible to the outside, not about how it does it. This has a great advantage: if no other class knows how our information is stored, then we can easily change how it is stored without breaking other classes. We can enforce this separation of what and how by making the fields private and using an accessor method to access them. 6.4 Responsability driven design (to redouce coupling) We have seen in the previous section that making use of proper encapsulation reduces coupling and can significantly reduce the amount of work needed to make changes to an application. Encapsulation, however, is not the only factor that influences the degree of coupling. Another aspect is known by the term responsibility-driven design. Responsibility-driven design expresses the idea that each class should be responsible for handling its own data. Often, when we need to add some new functionality to an application, we need to ask ourselves in which class we should add a method to implement this new function. Which class should be responsible for the task? The answer is that the class that is responsible for storing some data should also be responsible for manipulating it. 6.5 Refactoring Refactoring is the activity of restructuring existing classes and methods to adapt them to changed functionality and requirements. Often in the lifetime of an application, functionality is gradually added. One common effect is that, as a side-effect of this, methods and classes slowly grow in length. Refactoring is the rethinking and redesigning of class and method structures. Most commonly the effect is that classes are split in two or that methods are divided into two or more methods. Refactoring can also include the joining of multiple classes or methods into one, but that is less common than splitting. Refactoring per supporto multilingua (esempio) Tipo enumerativo Java http://docs.oracle.com/javase/tutorial/java/javaOO/enum.html public enum CommandWord { // A value for each command word, plus one for unrecognized // commands. GO, QUIT, HELP, UNKNOWN; } In un frammento di codice il tipo enumerativo potrebbe essere utilizzato come nel seguente esempio4: switch (commandWord) { case UNKNOWN: System.out.println("I don’t know what you mean..."); break; case HELP: 4 As of Java 7, strings can also be used as values in switch statements. In Java 6 and earlier, strings cannot be used in switch statements. printHelp(); break; case GO: goRoom(command); break; case QUIT: wantToQuit = quit(command); break; } La gestione comandi testuali inseriti da un utente potrebbe essere fatta associando i comandi (stringhe) inserite dall'utente con i valori di tipo enumerativo. public makeCommads() { validCommands = new HashMap<String, CommandWord>(); validCommands.put("go", CommandWord.GO); validCommands.put("help", CommandWord.HELP); validCommands.put("quit", CommandWord.QUIT); } Con il procedimento ora indicato diventerebbe molto semplice effettuare un supporto multilingua per l'applicazione. Basterebbe creare una mappa per ogni lingua. 6.6 Metodi statici (keyword static) Oltre alle variabili static viste in § 5.7 esistono anche i metodi static. Un metodo static può essere invocato riferendosi al nome della classe e non al riferimento di un oggetto di quella classe. Ad esempio: http://www.tutorialspoint.com/java/java_nonaccess_modifiers.htm public class InstanceCounter { private static int numInstances = 0; public static int getCount() { return numInstances; } private static void addInstance() { numInstances++; } InstanceCounter() {// nel costruttore richiamo il metodo addInstance //riferendomi alla classe InstanceCounter InstanceCounter.addInstance(); } public static void main(String[] arguments) { System.out.println("Starting with " + InstanceCounter.getCount() + " instances"); for (int i = 0; i < 500; ++i){ new InstanceCounter(); } System.out.println("Created " + InstanceCounter.getCount() + " instances"); } } This would produce the following result: Started with 0 instances Created 500 instances Il metodo Main (rivisitato) Un programma Java che non sia eseguito da BlueJ, ma ad esempio da console deve avere un metodo main che rappresenta il primo metodo eseguito quando si lancia l'applicazione. Tale metodo è un metodo static perché esso deve essere eseguito prima ancora che si possa creare alcun oggetto. Il metodo main di Java è simile al Main di C# visto in precedenza. http://docs.oracle.com/javase/tutorial/getStarted/application/index.html /** * The HelloWorldApp class implements an application that * simply displays "Hello World!" to the standard output. */ class HelloWorldApp { public static void main(String[] args) { System.out.println("Hello World!"); //Display the string. } } La classe Math Un esempio di classe che contiene tanti (e solo) metodi statici è la classe java.lang.Math http://docs.oracle.com/javase/7/docs/api/java/lang/Math.html Ad esempio, per utilizzare il metodo che effettua il logaritmo naturale di un numero double basta scrivere Math.log(myNumber). Si noti che Math essendo in java.lang non richiede di utilizzare import java.lang.Math, Testing is an activity that is concerned with finding out whether a segment of code contains any errors. Testing well is not easy, and there is much to think about when testing a program. Debugging comes after testing. If tests have shown that an error is present, we use debugging techniques to find out exactly where the error is and how to fix it. There can be a significant amount of work between knowing that an error exists and finding the cause and fixing it. Writing for maintainability is maybe the most fundamental topic. It is about trying to write code in such a way that errors are avoided in the first place and, if they still slip in, that they can be found as easily as possible. Code style and commenting are part of it, as are the code quality principles that we have discussed in the previous chapter. Ideally, code should be easy to understand so that the original programmer avoids introducing errors and a maintenance programmer can easily find possible errors. In practice, this is not always simple. But there are big differences between few and many errors and also between the effort it takes to debug well-written code and not-so-well-written code. 7.1 Testing con BlueJ Aprire il progetto online-shop. Ispezionare il codice per capire cosa fa. Provare a usare il codice creando qualche oggetto SalesItem e inserire alcuni commenti per ogni oggetto SalesItem. Effettuare i test riportati negli esercizi 7.1 fino a 7.10. Quando si testa un'applicazione è possibile eseguire due tipologie di testing. Il test positivo e il test negativo. Test positivo: è un test fatto per verificare che le funzionalità dell'applicazione siano quelle attese. Ad esempio per verificare che un metodo restituisca un valore atteso per una data lista di valori dell'input. Test negativo: è un test fatto per verificare che le funzionalità dell'applicazione non diano il risultato atteso quando i valori dell'input non rientrano nei limiti previsti per l'imput. Ad esempio un metodo che dovrebbe accettare un numero compreso tra un valore minimo e un valore massimo, non dovrebbe dare il risultato atteso quando il valore dell'input non è compreso nell'intervallo dei valori ammessi. 7.2 Test automatici I test sul software sono una parte molto importante dello sviluppo del software, ma richiedono molto tempo e talvolta, essendo ripetitivi, sono molto noiosi. È importante avere degli strumenti che permettano di effettuare test ripetitivi automatici. In particolare i test di regressione sul software vanno a verificare che alcune funzionalità di un'applicazione continuino a funzionare anche quando si apportano modifiche o si introducono funzionalità aggiuntive. JUnit BlueJ utilizza il framework JUnit ( http://junit.org/ ), largamente impiegato anche in altri ambienti di sviluppo per effettuare test automatici sul codice java. È un framework che semplifica e automatizza la creazione di test sulle classi. L'aspetto veramente interessante di JUnit è che esso rende possibile creare test sulle classi sin dalla stesura delle prime funzionalità permettendo di testare tutto il codice prodotto, durante la sua creazione. Per imparare a usare JUint apriamo il progetto online-shop-junit. Questo progetto contiene le stesse classi del progetto online-shop con l'aggiunta della classe SalesItemTest. La classe SalesItemTest è una classe annotata come <<unit test>> ed è colorata in maniera differente dalle normali classi. Spostando la classe SalesItem si sposta anche la classe SalesItemTest dal momento che quest'ultima è una classe di testing associata alla classe SalesItem. Per utilizzare Junit in BlueJ bisogna abilitare i tools per i test di regressione andando in Tools>Preferences>Interfaces, spuntando l'opzione “show unit testing tools” e confermando la selezione su ok. Dopo aver abilitato i tools per il testing è possibile effettuare i test o cliccando su “Run Tests” oppure facendo clic col tasto sinistro del mouse sulla classe SalesItemTest e selezionando il test che si vuole effettuare. Nel progetto online-shop-junit la classe SalesItemTest è già stata creata e contiene già alcuni test di regressione. Possiamo lanciare i test cliccando su RunTests. Otteniamo lo stesso risultato se selezioniamo “Test All” facendo click con il tasto sinistro del mouse sulla classe SalesItemTest. Facendo click con il tasto sinistro del mouse su SalesItemTest possiamo anche lanciare in maniera selettiva uno dei test di regressione presenti. Analizziamo il codice di un test, ad esempio il test che verifica i commenti per inseriti per un articolo: /** * Test that a comment can be added, and that the comment count is correct afterwards. */ @Test public void testAddComment() { SalesItem salesIte1 = new SalesItem("Java for complete Idiots", 21998); assertEquals(true, salesIte1.addComment("James Duckling", "This book is great. I learned all my Java from it.", 4)); assertEquals(1, salesIte1.getNumberOfComments()); } Il metodo testAddComment crea un oggetto SalseItem chiamato salesIte1 e poi vi aggiunge un commento, verificando che il valore restituito da addComment sia true. In questo codice è fondamentale il metodo assertEquals che verifica l'uguaglianza tra il valore restituita da un metodo e il valore atteso. Il metodo assertEquals è poi utilizzato per verificare che il metodo getNumberOfComments restituisca un solo commento per l'articolo salesIte1. I test di regressione possono essere scritti manualmente seguendo lo schema del metodo testAddComment (usando l'annotazione @Test prima del metodo e poi usando gli assert di Junit), ma uno degli aspetti più interessanti dell'uso di Junit in BlueJ è che la scrittura dei test di regressione può essere automatizzata registrando una macro. Una fixture è un insieme di oggetti in un determinato stato che servono da base per l'esecuzione di tutti i test di una unit test. Supponiamo che per eseguire i test bisogna creare due oggetti SalesItem e che su uno di questi bisogna aggiungere un commento. Anziché creare gli oggetti prima di ogni test è possibile creare una fixture nel seguente modo: si creano gli oggetti nel solito modo nell'object bench e poi facendo click con il tasto sinistro del mouse sulla classe di test si seleziona l'opzione “Object Bench to Test Fixture”. Il risultato sarà che gli oggetti scompaiono dall'object bench e compaiono nel metodo setUp della classe di test. Se in seguito si volesse modificare lo stato degli oggetti della fixture basterebbe selezionare sulla classe di test l'opzione “Test Fixture to Object Bench” effettuare le modifiche volute e poi richiamare “Object Bench to Test Fixture”. 7.3 Debugging Quando un test fallisce o ci si accorge che il programma non funziona bisogna trovare l'errore (o gli errori). Questa attività si chiama debugging e può essere svolta con varie tecniche che possono andare dall'analisi del codice all'uso del debugger come già mostrato in precedenza. 7.4 Per ripassare Documentazione ufficiale Oracle - Learning the Java Language: Table of Contents: http://docs.oracle.com/javase/tutorial/java/TOC.html Guida rapida con esempi: Tutorialspoint.com http://www.tutorialspoint.com/java/ La documentazione ufficiale delle API e dei tools Java: http://docs.oracle.com/javase/7/docs/