Esercizi
- Home
- Esercizi Capitolo 7
Capitolo 7
Polimorfismo
Per questo capitolo simuleremo la costruzione di un IDE di programmazione in maniera molto
semplificata. Anche in questo caso sarà un esercizio incrementale quindi bisogna completare ogni
esercizio (e leggerne la soluzione) per poter passare al successivo.
Creeremo classi ed interfacce andando per gradi. In particolare creeremo le astrazioni di
IDE
, Editor
, FileSorgente
, File
,
TipoFile
e così via. L'esercizio è guidato, quindi il lettore è sollevato dalla
responsabilità di decidere quali classi devono comporre la nostra applicazione. Per tale ragione si
tratta di esercizi di programmazione più che di analisi e progettazione.
Con i prossimi esercizi ci sarà solo da applicare le definizioni che abbiamo imparato sino ad ora,
non è richiesto quasi nessun tipo di algoritmo. Lo scopo finale è di concentrarsi sull'Object
Orientation e non sugli algoritmi. Inoltre sono presentati anche altri tipi di esercizi come quelli
che supportano la preparazione alla certificazione Oracle.
Di seguito trovate gli esercizi del capitolo.
Per ogni esercizio, cliccando sulla traccia potete vedere la relativa soluzione.
Gli esercizi caratterizzati dall'icona sono
considerati i più complessi relativamente agli argomenti trattati.
Se preferite lavorare offline, è possibile scaricare tutti gli esercizi e le relative
soluzioni in formato PDF nella sezione download.
-
Esercizio 7.a) Polimorfismo per metodi, Vero o Falso:
1. L'overload di un metodo implica scrivere un altro metodo con lo stesso nome e diverso tipo di ritorno.
2. L'overload di un metodo implica scrivere un altro metodo con nome differente e stessa lista di parametri.
3. La segnatura (o firma) di un metodo è costituita dalla coppia identificatore - lista di parametri.
4. Per sfruttare l'override bisogna che sussista l'ereditarietà.
5. Per sfruttare l'overload bisogna che sussista l'ereditarietà.
6. Supponiamo che in una classeB
, la quale estende la classeA
, ereditiamo il metodo:Se nella classepublic int m(int a, String b) { ... }
B
scriviamo il metodo:stiamo facendo overload e non override.public int m(int c, String b) { ... }
7. Se nella classeB
scriviamo il metodo:stiamo facendo overload e non override.public int m(String a, String b) { ... }
8. Se nella classeB
scriviamo il metodo:otterremo un errore in compilazione.public void m(int a, String b) { ... }
9. Se nella classeB
scriviamo il metodo:otterremo un errore in compilazione.protected int m(int a, String b) { ... }
10. Se nella classeB
scriviamo il metodo:otterremo un override.public int m(String a, int c) { ... }
Soluzione
1. Falso, l'overload di un metodo implica scrivere un altro metodo con lo stesso nome e diversa lista di parametri.
2. Falso, l'overload di un metodo implica scrivere un altro metodo con lo stesso nome e diversa lista di parametri.
3. Vero.
4. Vero.
5. Falso, l'overload di un metodo implica scrivere un altro metodo con lo stesso nome e diversa lista di parametri.
6. Falso, stiamo facendo override. L'unica differenza sta nel nome dell'identificatore di un parametro, che è ininfluente al fine di distinguere metodi.
7. Vero, la lista dei parametri dei due metodi è diversa.
8. Vero, in caso di override il tipo di ritorno non può essere differente.
9. Vero, in caso di override il metodo riscritto non può essere meno accessibile del metodo originale.
10. Falso, otterremo un overload. Infatti, le due liste di parametri differiscono per posizioni. -
Esercizio 7.b) Polimorfismo per dati, Vero o Falso:
1. Considerando le classi introdotte in questo capitolo, il seguente frammento di codice non produrrà errori in compilazione:
2. Considerando le classi introdotte in questo capitolo, il seguente frammento di codice non produrrà errori in compilazione:Veicolo v [] = {new Automobile(), new Aereo(), new Aereo()};
3. Considerando le classi introdotte in questo capitolo, il seguente frammento di codice non produrrà errori in compilazione:Object o [] = {new Automobile(), new Aereo(), "ciao"};
Aereo a [] = {new Veicolo(), new Aereo(), new Aereo()};
4. Considerando le classi introdotte in questo capitolo, e se il metodo della classe
Viaggiatore
fosse questo:potremmo passargli un oggetto di tipopublic void viaggia(Object o) { o.accelera(); }
Veicolo
senza avere errori in compilazione. Per esempio:5. Considerando le classi introdotte in questo capitolo, il seguente frammento di codice non produrrà errori in compilazione:claudio.viaggia(new Veicolo());
6. Considerando le classi introdotte in questo capitolo, il seguente frammento di codice non produrrà errori in compilazione:PuntoTridimensionale ogg = new Punto();
7. Considerando le classi introdotte in questo capitolo, il seguente frammento di codice non produrrà errori in compilazione:PuntoTridimensionale ogg = (PuntoTridimensionale)new Punto();
Punto ogg = new PuntoTridimensionale();
8. Considerando le classi introdotte in questo capitolo, e se la classe
Piper
estende la classeAereo
, il seguente frammento di codice non produrrà errori in compilazione:9. Considerando le classi introdotte in questo capitolo, il seguente frammento di codice non produrrà errori in compilazione:Veicolo a = new Piper();
10. Considerando le classi introdotte in questo capitolo. Il seguente frammento di codice non produrrà errori in compilazione:String stringa = fiat500.toString();
public void pagaDipendente(Dipendente dip) { if (dip instanceof Dipendente) { dip.stipendio = 1000; } else if (dip instanceof Programmatore) { ... } }
Soluzione
1. Vero.
2. Vero.
3. Falso, la classeVeicolo
essendo astratta non è istanziabile. Inoltre non è possibile inserire in una collezione eterogenea di aerei unVeicolo
che è superclasse diAereo
.
4. Falso, la compilazione fallirebbe già dal momento in cui provassimo a compilare il metodo viaggia. Infatti non è possibile chiamare il metodo accelera con un reference di tipoObject
.
5. Falso, c'è bisogno di un casting, perché il compilatore non sa a priori il tipo a cui punterà il reference al runtime.
6. Vero.
7. Vero.
8. Vero, infattiVeicolo
è superclasse diPiper
.
9. Vero, il metodotoString
appartiene a tutte le classi perché ereditato dalla superclasseObject
.
10. Vero, ma tutti i dipendenti saranno pagati allo stesso modo. -
Esercizio 7.c)
Per iniziare, creare un'interfaccia
TipoFile
che definisca delle costanti statiche che rappresentano i tipi di file sorgente che vorremo gestire con l'IDE. Di sicuro una di queste costanti deve chiamarsiJAVA
, tutte le altre sono a piacere. Scegliere anche il tipo delle costanti a piacere.
Soluzione
Il listato potrebbe essere il seguente:
public interface TipoFile { int JAVA = 1; int C_SHARP = 2; int C_PLUS_PLUS = 3; int C = 4; }
Si noti che non è necessario specificare modificatori per le costanti dato che è sottointeso che siano dichiarate implicitamente
public
,static
efinal
. Inoltre abbiamo scelto come tipo delle costantiint
, ma un qualsiasi altro tipo sarebbe andato bene, l'importante è che le costanti abbiano valori differenti.Questo tipo di uso di interfacce è in disuso da anni, precisamente da quando in Java versione 5 sono state introdotte le cosiddette enumerazioni (cfr. capitolo 9). Tuttavia useremo ugualmente questa modalità di programmazione visto che non abbiamo ancora affrontato l'argomento enumerazioni. -
Esercizio 7.d)
Creare una classe
File
che astrae il concetto di file generico e che definisce un nome ed un tipo.
Soluzione
Il listato della classe
File
potrebbe essere il seguente:
public abstract class File { private String nome; private int tipo; public File(String nome, int tipo) { this.nome = nome; this.tipo = tipo; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public int getTipo() { return tipo; } public void setTipo(int tipo) { this.tipo = tipo; } }
Si noti che abbiamo dichiarato la classe astratta, perché generica e creata ai fini dell'estensione. -
Esercizio 7.e)
Creare una classe
FileSorgente
che astrae il concetto di file sorgente (estendendo la classeFile
) che definisce anche un contenuto di tipo stringa.
Soluzione
Il listato della classe
FileSorgente
dovrebbe essere il seguente:
public class FileSorgente extends File { private String contenuto; public FileSorgente(String nome, int tipo) { super(nome, tipo); } public FileSorgente(String nome, int tipo, String contenuto) { this(nome, tipo); this.contenuto = contenuto; } public String getContenuto() { return contenuto; } public void setContenuto(String contenuto) { this.contenuto = contenuto; } }
Si noti che abbiamo riutilizzato il costruttore della superclasse mediante la parola chiavesuper
, ed abbiamo creato anche un costruttore equivalente a quello della superclasse. -
Esercizio 7.f)
Aggiungere un metodo
aggiungiTesto
che aggiunga una stringa di testo alla fine del contenuto del file sorgente.
Soluzione
Il listato del metodo richiesto potrebbe essere:
public void aggiungiTesto(String testo) { if (contenuto == null) { contenuto = ""; } if (testo != null) { contenuto += testo; } }
Una stringanull
"sommata" ad un'altra stringa viene rappresentata proprio con la stringa "null". Da quest'affermazione si può intuire del perché abbiamo implementato il metodo in questo modo. -
Esercizio 7.g)
Aggiungere un overload del metodo
aggiungiTesto
che aggiunga una stringa di testo in un punto specificato del contenuto del file sorgente (consultare la documentazione della classeString
).
Soluzione
Il listato del metodo richiesto potrebbe essere:
public void aggiungiTesto(String testo, int posizione) { final int length = contenuto.length(); if (contenuto != null && testo != null && posizione > 0 && posizione < length) { contenuto = contenuto.substring(0, posizione) + testo + contenuto.substring(posizione); } }
Abbiamo scelto, per semplicità, di non aggiungere testo nel caso la posizione specificata non sia corretta. Tuttavia una clausolaelse
che stampi un messaggio di errore potrebbe rappresentare una soluzione migliore. In realtà questo metodo si può migliorare molto...
Nel capitolo 8 vedremo come si gestiscono veramente le eccezioni in Java. -
Esercizio 7.h)
Creare una classe
TestFileSorgente
che testi il funzionamento corretto della classeFileSorgente
.
Soluzione
Il listato per la classe TestFileSorgente potrebbe essere il seguente:
public class TestFileSorgente { public static void main(String args[]) { FileSorgente fileSorgente = new FileSorgente("Test.java", TipoFile.JAVA, "public class MyClass {\n\r"); System.out.println(fileSorgente.getContenuto()); // Test aggiungiTesto (String) corretto fileSorgente.aggiungiTesto("}"); System.out.println(fileSorgente.getContenuto()); // Test aggiungiTesto (String,int) corretto fileSorgente.aggiungiTesto("//Test aggiunta testo\n\r", 23); System.out.println(fileSorgente.getContenuto()); // Test aggiungiTesto (String,int) scorretto fileSorgente.aggiungiTesto("//Test aggiunta testo\n\r", -1); System.out.println(fileSorgente.getContenuto()); // Test aggiungiTesto (String,int) scorretto fileSorgente.aggiungiTesto("//Test aggiunta testo\n\r", 100); System.out.println(fileSorgente.getContenuto()); } }
L'output risultante è il seguente:
public class MyClass { public class MyClass { } public class MyClass { //Test aggiunta testo } public class MyClass { //Test aggiunta testo } public class MyClass { //Test aggiunta testo }
Si potrebbero scrivere molti più casi di test, e si dovrebbe anche fare in modo che ogni caso di test non dipenda dal precedente, ma per ora va bene così.Per effettuare casi di test con maggiore comodità, è consigliato l'utilizzo di un tool come JUnit. Potete trovare una breve descrizione di JUnit all'interno dell'approfondimento 8.2. -
Esercizio 7.i)
Creare un'interfaccia
Editor
che astrae il concetto di editor di testo. Bisogna definire metodi per aprire, chiudere, salvare e modificare un file.
Il listato dell'interfaccia richiesta potrebbe essere:
public interface Editor { default void salva(FileSorgente file) { System.out.println("File: " + file.getNome() + " salvato!"); } default void apri(FileSorgente file) { System.out.println("File: " + file.getNome() + " aperto!"); } default void chiudi(FileSorgente file) { System.out.println("File: " + file.getNome() + " chiuso!"); } default void modifica(FileSorgente file, String testo) { System.out.println("File: " + file.getNome() + " modificato!"); } }
Abbiamo creato dei metodi di default che simulano con la stampa la loro effettiva esecuzione. -
Esercizio 7.j)
Creare un'interfaccia
IDE
che astrae il concetto di IDE di sviluppo. Si tenga presente che un IDE è anche un editor. Bisogna definire metodi per compilare ed eseguire un file.
Soluzione
Il listato dell'interfaccia richiesta potrebbe essere:
public interface IDE extends Editor { default void compila(FileSorgente file) { System.out.println("File: " + file.getNome() + " compilato!"); } default void esegui(FileSorgente file) { System.out.println("File: " + file.getNome() + " eseguito!"); } }
Anche in questo caso abbiamo creato dei metodi di default che simulano con la stampa la loro effettiva esecuzione. -
Esercizio 7.k)
Creare una semplice implementazione
JavaIDE
dell'interfacciaIDE
. Aggiungere un'implementazione per il metodo modifica (e a piacere è possibile reimplementare tutti i metodi che si ritiene utile).
Soluzione
Il listato della classe richiesta potrebbe essere:
public class JavaIDE implements IDE { @Override public void modifica(FileSorgente file, String testo) { IDE.super.modifica(file, testo); file.aggiungiTesto(testo); System.out.println("Contenuto modificato:\n" + file.getContenuto()); } }
-
Esercizio 7.l)
Creare una classe di test
TestIDE
che esegue delle operazioni sul file tramiteIDE
.
L'esercizio potrebbe poi continuare estendendo ulteriormente queste classi, sentitevi liberi di compiere altre iterazioni di programmazione dopo aver completato tutto. Dovrete darvi delle specifiche, capire come implementarle e implementarle: tre passi.
Soluzione
Il listato della classe richiesta potrebbe essere:
public class TestIDE { public static void main(String args[]) { IDE ide = new JavaIDE(); FileSorgente fileSorgente = new FileSorgente("Test.java", TipoFile.JAVA, "public class MyClass {\n\r"); ide.modifica(fileSorgente, "}"); } }
L'output risultante sarà:
File: Test.java modificato! Contenuto modificato: public class MyClass { }
-
Esercizio 7.m)
Partendo dalle soluzioni dell'esercizio 6.k, nel quale abbiamo creato delle classi per simulare una libreria, eseguire i seguenti passi:
1. Creare i packagelibreria.dati
,libreria.business
,libreria.util
elibreria.interfaccia
, e posizionare le classi esistenti all'interno dei package che si ritengono più opportuni.
2. Creare per le classiLibro
eAlbum
, il metodotoString
, che restituisca una riga descrittiva dell'articolo. In particolare, il metodotoString
dovrà stampare queste informazioni:tipo_articolo
: (isbn
) - "titolo
" diautore
,genere
,prezzo
. Per esempio:Libro: (2345) "I pilastri della terra" di Ken Follet, Romanzo, 18€
,
3. Modificare la classeTestArticoli
, per testare anche il funzionamento del metodotoString
.
Soluzione
Inseriamo le classi
Articolo
,Album
eLibro
nel packagelibreria.dati
, le classiGenereUtils
,GenereLetterarioUtils
eGenereMusicaleUtils
all'interno del packagelibreria.util
, la classeTestArticoli
all'interno del packagelibreria.test
, infine lasciamo vuoto il packagelibreria.business
, visto che per ora non abbiamo ancora delle classi che incapsulano la logica di business.
Per creare nelle classiLibro
eAlbum
i metoditoString
richiesti, andiamo a sfruttare anche la loro superclasse astrattaArticolo
, che riscriviamo di seguito:
package libreria.dati; public abstract class Articolo { private static final char VALUTA ='€'; private String id; private String titolo; private String nome; private int prezzo; private String genere; public Articolo(String id, String titolo, String nome, int prezzo, String genere) { super(); setId(id); setTitolo(titolo); setNome(nome); setPrezzo(prezzo); setGenere(genere); } public String getId() { return id; } public void setId(String id) { this.id = id; } public String getTitolo() { return titolo; } public void setTitolo(String titolo) { this.titolo = titolo; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public int getPrezzo() { return prezzo; } public void setPrezzo(int prezzo) { this.prezzo = prezzo; } public String getGenere() { return genere; } public void setGenere(String genere) { this.genere = genere; } @Override public String toString() { return ": (" + getId() + ") " + getTitolo() + " di "+ getNome() + ", " + getGenere() + ", " + getPrezzo() + " " + VALUTA; } }
Notare che abbiamo anche introdotto una costante privata e staticaVALUTA
per rappresentare la valuta del prezzo.
Possiamo quindi riscrivere il metodotoString
nella sottoclasseLibro
nel seguente modo:
package libreria.dati; import libreria.util.*; public class Libro extends Articolo { public Libro(String isbn, String titolo, String autore, int prezzo, String genere) { super(isbn, titolo, autore, prezzo, genere); } public void setGenere(String genere) { if (GenereLetterarioUtils.isGenereValido(genere)) { super.setGenere(genere); } else { GenereLetterarioUtils.stampaErrore(genere); } } @Override public String toString() { return "Libro" + super.toString(); } }
E possiamo quindi riscrivere il metodotoString
nella sottoclasseAlbum
nel seguente modo:
package libreria.dati; import libreria.util.*; public class Album extends Articolo { public Album(String ismn, String titolo, String artista, int prezzo, String genere) { super(ismn, titolo, artista, prezzo, genere); } public void setGenere(String genere) { if (GenereMusicaleUtils.isGenereValido(genere)) { super.setGenere(genere); } else { GenereMusicaleUtils.stampaErrore(genere); } } @Override public String toString() { return "Album"+ super.toString(); } }
Infine modifichiamo la classeTestArticoli
nel seguente modo:
package libreria.test; import libreria.dati.*; import libreria.util.*; public class TestArticoli { public static void main(String[] args) { Libro jfaVol1 = new Libro("979-12-200-4915-3", "Java for Aliens Vol. 1", "Claudio De Sio Cesari", 25, GenereLetterarioUtils.MANUALE); Libro jfaVol2 = new Libro("979-12-200-4916-0", "Java for Aliens Vol. 2", "Claudio De Sio Cesari", 25, "NonEsistente"); // System.out.println("Genere JFA Vol 1 = " + jfaVol1.getGenere()); // System.out.println("Genere JFA Vol 2 = " + jfaVol2.getGenere()); System.out.println(jfaVol1); System.out.println(jfaVol2); Album lad = new Album("979-0-236-44-3", "Live after Death", "Iron Maiden", 25, GenereMusicaleUtils.ROCK); Album mop = new Album("978-0-789-01-2", "Master of Puppets", "Metallica", 25, "NonEsistente"); // System.out.println("Genere Live after Death = " + lad.getGenere()); // System.out.println("Genere Master of Puppets = " + mop.getGenere()); System.out.println(lad); System.out.println(mop); } }
Ovviamente il metodotoString
viene chiamato automaticamente dal metodoprintln
. L'output della precedente classe sarà il seguente:
Genere NonEsistente non valido! Usare uno dei seguenti generi: Romanzo Saggio Thriller Manuale Fantascienza Libro: (979-12-200-4915-3) Java for Aliens Vol. 1 di Claudio De Sio Cesari, Manuale, 25 € Libro: (979-12-200-4916-0) Java for Aliens Vol. 2 di Claudio De Sio Cesari, null, 25 € Genere NonEsistente non valido! Usare uno dei seguenti generi: Rock Jazz Blues Pop Rap Album: (979-0-236-44-3) Live after Death di Iron Maiden, Rock, 25 € Album: (978-0-789-01-2) Master of Puppets di Metallica, null, 25 €
-
Esercizio 7.n)
Partendo dalla soluzione dell'esercizio precedente, creare una classe
Carrello
, che rappresenti un carrello della spesa. Tale astrazione deve dichiarare i metodi: aggiungi per aggiungere un elemento al carrello,calcolaPrezzo
che restituisce il prezzo totale degli articoli presenti nel carrello,toString
che stampi la descrizione degli elementi degli articoli contenuti nel carrello. Il carrello potrà contenere un massimo di quattro articoli (libri o album). Fare in modo che il metodoaggiungi
stampi un errore quando si prova ad aggiungere il quinto articolo, e che l'elemento non venga aggiunto.
Creare la classeTestCarrello
, per testare il corretto funzionamento della classeCarrello
, invocando tutti i metodi definiti, e provare ad aggiungere undici articoli per verificare il corretto funzionamento implementato nel metodo precedente. Correggere eventuali bug.
Soluzione
Potremmo implementare la classe
Carrello
nel seguente modo:
public class Carrello { private static final int MASSIMO_NUMERO_DI_ARTICOLI = 4; private Articolo[] articoli; public Carrello() { articoli = new Articolo[MASSIMO_NUMERO_DI_ARTICOLI]; } public void aggiungi(Articolo articolo) { for (int i = 0; i < articoli.length; i++) { if (articoli[i]==null) { articoli[i] = articolo; return; } } System.out.println("Articolo non inserito: carrello pieno!"); } public boolean isVuoto() { return articoli[0] == null; } public int calcolaPrezzo() { int prezzoCarrello = 0; for (Articolo articolo : articoli) { prezzoCarrello += articolo.getPrezzo(); } return prezzoCarrello; } public String toString() { String descrizioneCarrello = "Il carrello contiene i seguenti articoli:\n"; for (Articolo articolo : articoli) { descrizioneCarrello += articolo + "\n"; } return descrizioneCarrello; } }
Mentre potremmo implementare la classeTestCarrello
(all'interno del packagelibreria.test
) nel seguente modo:
package libreria.test; import libreria.dati.*; import libreria.util.*; public class TestCarrello { public static void main(String[] args) { Libro jfaVol1 = new Libro("979-12-200-4915-3", "Java for Aliens Vol. 1", "Claudio De Sio Cesari", 25, GenereLetterarioUtils.MANUALE); Libro jfaVol2 = new Libro("979-12-200-4916-0", "Java for Aliens Vol. 2", "Claudio De Sio Cesari", 25,GenereLetterarioUtils.MANUALE); Album lad = new Album("979-0-236-44-3", "Live after Death", "Iron Maiden", 25, GenereMusicaleUtils.ROCK); Album mop = new Album("978-0-789-01-2", "Master of Puppets", "Metallica", 25,GenereMusicaleUtils.ROCK); Album tt = new Album("978-0-789-01-9", "Tokyo Tapes", "Scorpions", 22, GenereMusicaleUtils.ROCK); Carrello carrello = new Carrello(); System.out.println("Carrello vuoto = " + carrello.isVuoto()); System.out.println("Aggiungiamo il libro "+ jfaVol1); carrello.aggiungi(jfaVol1); System.out.println("Aggiungiamo il libro "+ jfaVol2); carrello.aggiungi(jfaVol2); System.out.println("Aggiungiamo l'album "+ lad); carrello.aggiungi(lad); System.out.println("Aggiungiamo l'album "+ mop); carrello.aggiungi(mop); System.out.println("Aggiungiamo l'album "+ tt); carrello.aggiungi(tt); System.out.println("Carrello vuoto = " + carrello.isVuoto()); System.out.println(carrello); } }
Segue l'output della classeTestCarrello
:
Carrello vuoto = true Aggiungiamo il libro Libro: (979-12-200-4915-3) Java for Aliens Vol. 1 di Claudio De Sio Cesari, Manuale, 25 € Aggiungiamo il libro Libro: (979-12-200-4916-0) Java for Aliens Vol. 2 di Claudio De Sio Cesari, Manuale, 25 € Aggiungiamo l'album Album: (979-0-236-44-3) Live after Death di Iron Maiden, Rock, 25 € Aggiungiamo l'album Album: (978-0-789-01-2) Master of Puppets di Metallica, Rock, 25 € Aggiungiamo il libro Album: (978-0-789-01-9) Tokyo Tapes di Scorpions, Rock, 22 € Articolo non inserito: carrello pieno! Carrello vuoto = false Il carrello contiene i seguenti articoli: Libro: (979-12-200-4915-3) Java for Aliens Vol. 1 di Claudio De Sio Cesari, Manuale, 25 € Libro: (979-12-200-4916-0) Java for Aliens Vol. 2 di Claudio De Sio Cesari, Manuale, 25 € Album: (979-0-236-44-3) Live after Death di Iron Maiden, Rock, 25 € Album: (978-0-789-01-2) Master of Puppets di Metallica, Rock, 25 €
-
Esercizio 7.o) Varargs, Vero o Falso:
Quali di queste affermazioni sono vere? 1. I varargs permettono di utilizzare i metodi come se fossero degli overload.
2. La seguente dichiarazione è compilabile correttamente:3. La seguente dichiarazione è compilabile correttamente:public void myMethod(String... s, Date d) { ... }
4. Considerando il seguente metodo:public void myMethod(String... s, Date d...) { ... }
la seguente invocazione è corretta:public void myMethod(Object... o) { ... }
5. La seguente dichiarazione è compilabile correttamente:oggetto.myMethod();
6. Considerando il seguente metodo:public void myMethod(Object o, Object os...) { ... }
la seguente invocazione è corretta:public void myMethod(int i, int... is) { ... }
7. Le regole dell'override cambiano con l'introduzione dei varargs.oggetto.myMethod(new Integer(1));
8. Il metodo dijava.io.PrintStream
printf
è basato sul metodoformat
della classejava.util.Formatter
.
9. Il metodoformat
dijava.util.Formatter
non ha overload perché definito con un varargs.
10. Nel caso in cui si passi un array come varargs al metodoprintf
dijava.io.PrintStream
, questo verrà trattato non come oggetto singolo ma come se fossero stati passati ad uno ad uno ogni suo elemento.
Soluzione
1. Vero.
2. Falso.
3. Falso.
4. Vero.
5. Vero.
6. Vero.
7. Falso.
8. Vero.
9. Falso.
10. Vero. -
Esercizio 7.p)
Data la seguente gerarchia:
interface A { void metodo(); } interface B extends A { static void metodoStatico() {} } final class C implements B {} public abstract class D implements A { @Override void metodo() {} }
Scegliere tutte le affermazioni vere:
1. La classeC
non eredita il metodometodoStatico
.
2. L'interfacciaB
non può implementare un'altra interfaccia.
3. La classeC
implementandoB
eredita anche il metodo astrattometodo
diA
, e non essendo dichiarata astratta non può essere compilata.
4. La classeD
non compila perché non può essere dichiarataabstract
. Infatti non dichiara alcun metodo astratto.
5. La classeD
non compila perché il metodo che dichiara non è dichiaratopublic
.
6. La classeD
compila solo perché il metodo è annotato conOverride
.
Soluzione
Le risposte vere sono la 1, la 3 e la 5. La numero 5 è vera perché il metodo ereditato è implicitamente pubblico, e ridefinendolo senza il modificatore
public
, si sta cercando di renderlo meno accessibile di quello ereditato. Infatti compilando la classeD
otterremo il seguente errore:
error: metodo() in D cannot implement metodo() in A void metodo() {} ^ attempting to assign weaker access privileges; was public 1 error
-
Esercizio 7.q)
Quali delle seguenti affermazioni è corretta (scegliere tutte quelle corrette):
1. Un'interfaccia estende la classeObject
.
2. Un metodo che prende come parametro un reference di tipoObject
, può prendere in input qualsiasi oggetto di qualsiasi tipo, anche di tipo interfaccia.
3. Un metodo che prende come parametro un reference di tipoObject
, può prendere in input qualsiasi oggetto di qualsiasi tipo, anche un array.
4. Un metodo che prende come parametro un reference di tipoObject
, può prendere in input qualsiasi oggetto di qualsiasi tipo, anche una collezione eterogenea.
5. Tutti i cast di oggetti, sono valutati al tempo di compilazione.
Soluzione
Le affermazioni corrette sono la 2, la 3 e la 4.
-
Esercizio 7.r)
Considerando le seguenti classi:
public class StampaNumero { public void stampa(double numero) { System.out.print(numero); } } public class StampaIntero extends StampaNumero { public void stampa(int numero) { System.out.print(numero); } public static void main(String args[]) { StampaNumero stampaNumero = new StampaIntero(); stampaNumero.stampa(1); } }
Eseguendo
StampaIntero
, qual è l'output di questo programma?1. 1.2
2. 1
3. 1.0
4. 11.2
Soluzione
La risposta giusta è la numero 3. Infatti, viene chiamato sempre il metodo
stampa
della superclasseStampaNumero
che prende in input undouble
, e questo spiega il formato dell'output. Il motivo per cui non viene chiamato in maniera virtuale il metodo nella sottoclasseStampaIntero
, è perché non si tratta di un override, visto che il tipo di parametro è diverso tra i due metodi. Non trattandosi di un override, usando un reference della superclasse, l'unico metodo che si può chiamare, è proprio quello della superclasse. -
Esercizio 7.s)
Considerate la seguente gerarchia:
E la seguente classe di test:public interface Satellite { void orbita(); } public class Luna implements Satellite { @Override public void orbita() { System.out.println("Luna che orbita"); } } public class SatelliteArtificiale implements Satellite { @Override public void orbita() { System.out.println("Satellite artificiale che orbita"); } }
public class TestSatelliti { public static void main(String args[]) { test(new Luna(), new SatelliteArtificiale()); Satellite[] satelliti = { new Luna(), new SatelliteArtificiale() }; test(satelliti); test(); test(new Object()); } public static void test(Satellite... satelliti) { for (Satellite satellite : satelliti) { satellite.orbita(); } } }
Scegliere tutte le affermazioni corrette:1. L'applicazione compila e viene eseguita senza errori
2. L'applicazione non compila per l'istruzionetest(satelliti)
;
3. L'applicazione non compila per l'istruzionetest();
4. L'applicazione non compila per l'istruzionetest(new Object())
5. L'applicazione compila ma al runtime si blocca per un'eccezione.
Soluzione
L'unica risposta corretta è la numero 4.
-
Esercizio 7.t)
Definire l'overload e l'override. E fare un esempio di una sottoclasse, che implementa entrambi i concetti.
Soluzione
Overload: dal momento che un metodo è univocamente determinato dalla sua firma, in una classe (o un'interfaccia) è possibile creare più metodi con lo stesso identificatore ma con differente lista di parametri. In casi come questo si parla di overload di un (nome di un) metodo.
Override: permette di riscrivere in una sottoclasse un metodo ereditato da una superclasse (o interfaccia).
Un esempio di sottoclasse, che implementa entrambi i concetti, lo possiamo ricavare modificando le classi dell'esercizio 7.r:
public class StampaNumero { public void stampa(double numero) { System.out.print(numero); } } public class StampaIntero extends StampaNumero { //overload public void stampa(int numero) { System.out.print(numero); } //override public void stampa(double numero) { System.out.print(numero); } }
-
Esercizio 7.u)
Tenendo presente che
Number
è superclasse della classeInteger
, consideriamo la seguente gerarchia:
public abstract class SommaNumero { public abstract Number somma (Number n1, Number n2); } public class SommaIntero extends SommaNumero { @Override public Integer somma(Number n1, Number n2) { return (Integer)n1 + (Integer)n2; } }
Scegliere tutte le affermazioni corrette:1. La classeSommaIntero
compila senza errori.
2. La classeSommaIntero
non compila perché l'override non è corretto: i tipi di ritorno non coincidono.
3. La classeSommaIntero
non compila perché non è possibile utilizzare l'operatore+
se non con numeri di tipo primitivo.
4. La classeSommaIntero
potrebbe causare un'eccezione al runtime.
Soluzione
Le affermazioni corrette sono le numero 1 e 4.
L'affermazione 2 non è corretta perché il tipo di ritorno della classeSommaIntero
è covariante (cfr. paragrafo 7.2.3.1). L'affermazione 3 invece non è corretta perché sussiste l'autoboxing-unboxing.
La numero 4 è corretta perché se per esempio eseguissimo questo codice:
SommaIntero sommaIntero = new SommaIntero(); sommaIntero.somma(1.0, 1.0);
otterremo quest'eccezione al runtime:
Exception in thread "main" java.lang.ClassCastException: java.base/java.lang.Double cannot be cast to java.base/java.lang.Integer at SommaIntero.somma(SommaIntero.java:4) at SommaIntero.main(SommaIntero.java:9)
Alla gestione delle eccezioni è dedicato gran parte del capitolo 8. -
Esercizio 7.v)
Rendere robusto il metodo
somma
della classeSommaIntero
definito nell'esercizio 7.u, in modo che al runtime funzioni senza generare eccezioni.
Soluzione
Una possibile soluzione potrebbe essere la seguente:
public class SommaIntero extends SommaNumero { @Override public Integer somma(Number n1, Number n2) { if (n1 == null || n2 == null) { System.out.println("Impossibile sommare un operando null, " + "restituisco il valore di default"); return Integer.MIN_VALUE; } else if (!(n1 instanceof Integer && n2 instanceof Integer)) { System.out.println("Passa solo variabili di tipo intere, " + "restituisco il valore di default"); return Integer.MIN_VALUE; } return (Integer)n1 + (Integer)n2; } }
Eseguendo il seguente metodomain
infatti:
public static void main(String args[]) { SommaIntero sommaIntero = new SommaIntero(); sommaIntero.somma(1.0, 1.0); sommaIntero.somma(null, 1.0); }
otterremo il seguente output e nessuna eccezione:
Passa solo variabili di tipo intere, restituisco il valore di default Impossibile sommare un operando null, restituisco il valore di default
Si noti che il primo controllo sulla nullità dei parametri è necessario perché non è possibile utilizzare il cast su una variabile nulla, e nemmeno utilizzare l'operatore+
. -
Esercizio 7.w)
Definire brevemente cos'è un parametro polimorfo, cosa sono le collezioni eterogenee, e cosa è una chiamata virtuale a un metodo
Soluzione
Un parametro polimorfo è un parametro di un metodo dichiarato di un certo tipo (magari astratto) ma che al runtime punterà ad un'istanza di una sua sottoclasse.
Le collezioni eterogenee sono collezioni di oggetti diversi, come per esempio un array diNumber
, che contiene oggetti delle sue sottoclassi comeInteger
.
Si parla di chiamata virtuale ad un metodo quando, utilizzando un reference di una superclasse che punta ad un oggetto di una, viene invocato un metodo che in realtà è ridefinito in una sottoclasse. -
Esercizio 7.x)
Partendo dalla soluzione dell'esercizio 6.y, facciamo evolvere la nostra applicazione che simula una rubrica:
1. Sostituire il metodostampaDettagli
con l'override del metodotoString
, nelle classiContatto
eSpeciale
. Questa modifica è coerente, perché tali classi rappresentano dati dell'applicazione, e quindi non dovrebbero "stampare dettagli". Fare in modo che il metodotoString
restituisca anche l'identificativo (variabileid
).
2. Nella classeRubrica
aggiungere all'array contatti anche gli elementi dell'arraycontattiSpeciali
, ed eliminare la variabilecontattiSpeciali
. Modificare i metodigetContatti
egetContattiSpeciali
per fare in modo che il primo restituisca tutti i contatti (speciali e non), mentre il secondo restituisca solo i contatti speciali.
3. Se ce n'è bisogno, modificare la classeUtente
per farla funzionare in maniera coerente alla nuova implementazione della classeRubrica
.
4. Modificare le classiRicercaContatti
, eRicercaContattiSpeciali
, affinché funzionino coerentemente con le modifiche effettuate.
Soluzione
Riscriviamo le classi
Contatto
eSpeciale
eliminando il metodostampaDettagli
, e lo sostituiamo con l'override del metodotoString
. Prima però inseriamo anche il seguente metodotoString
all'interno della classeEntita
:
@Override public String toString() { return "Id="+getId(); }
Richiamiamo il metodo precedente nel metodotoString
della sottoclasseContatto
nel seguente modo:
@Override public String toString() { return super.toString() +"\nNome="+ nome + "\nNumeroDiTelefono="+ numeroDiTelefono +"\nIndirizzo="+ indirizzo; }
Quindi possiamo semplificare molto il metodotoString
della classeSpeciale
:
@Override public String toString() { return super.toString() + "\nSuoneria="+ suoneria; }
Potremmo implementare la classeRubrica
nel seguente modo:
package rubrica.dati; public class Rubrica implements Dato { private static Rubrica instance; public Contatto[] contatti; private Rubrica () { contatti = new Contatto[] { new Contatto("Claudio De Sio Cesari","Via Java 13","131313131313"), new Contatto("Stevie Wonder","Via Musica 10","1010101010"), new Contatto("Gennaro Capuozzo", "Piazza Quattro Giornate 1","1111111111"), new Speciale("Mario Ruoppolo","Via Neruda 3","333333","Il Postino"), new Speciale("Vincenzo Malinconico", "Via dei Tribunali 8","888888","Tuca Tuca"), new Speciale("Logan Howlett","Piazza Canada 6","66666","Hurt") } ; } public static Rubrica getInstance() { if (instance == null) { instance = new Rubrica(); } return instance; } public Contatto[] getContatti() { return contatti; } public Speciale[] getContattiSpeciali() { Speciale[] contattiSpecialiTrovati = new Speciale[contatti.length]; for (int i = 0, j = 0; i < contatti.length; ++i) { if (contatti[i] == null) { break; } if (contatti[i] instanceof Speciale) { contattiSpecialiTrovati[j] = (Speciale)contatti[i]; j++; } } return contattiSpecialiTrovati; } }
Mentre la classeUtente
potrebbe subire piccole modifiche:
package rubrica.business; import rubrica.dati.*; public class Utente { public Contatto[] cercaContattiPerNome(String nome) { Contatto []contatti = Rubrica.getInstance().getContatti(); Contatto []contattiTrovati = new Contatto[contatti.length]; for (int i = 0, j = 0; i < contattiTrovati.length; i++) { if (contatti[i] == null) { break; } if (contatti[i].getNome().toUpperCase(). contains(nome.toUpperCase())) { contattiTrovati[j] = contatti[i]; j++; } } return contattiTrovati; } public Speciale[] cercaContattiSpecialiPerNome(String nome) { Speciale []contattiSpeciali = Rubrica.getInstance().getContattiSpeciali(); Speciale []contattiSpecialiTrovati = new Speciale[contattiSpeciali.length]; for (int i = 0, j = 0; i < contattiSpecialiTrovati.length; i++) { if (contattiSpeciali[i] == null) { break; } if (contattiSpeciali[i].getNome().toUpperCase(). contains(nome.toUpperCase())) { contattiSpecialiTrovati[j] = contattiSpeciali[i]; j++; } } return contattiSpecialiTrovati; } }
Anche per le classiRicercaContatti
eRicercaContattiSpeciali
le modifiche sono minime e riguardano la stampa degli oggetti utilizzando il metodotoString
, invece del metodostampaDettagli
. Segue la classeRicercaContatti
:
package rubrica.interfaccia; import rubrica.dati.*; import rubrica.business.Utente; import java.util.Scanner; public class RicercaContatti { public static void main(String args[]) { System.out.println("Ricerca Contatti"); System.out.println(); var utente = new Utente(); System.out.println("Inserisci nome o parte del nome da ricercare"); Scanner scanner = new Scanner(System.in); String input = scanner.nextLine(); Contatto[] contattiTrovati = utente.cercaContattiPerNome(input); System.out.println("Contatti trovati con nome contenente \"" + input +"\"" ); for (Contatto contatto : contattiTrovati) { if (contatto != null) { System.out.println(contatto+"\n"); } } } }
Ed infine la classeRicercaContattiSpeciali
:
package rubrica.interfaccia; import rubrica.dati.*; import rubrica.business.Utente; import java.util.Scanner; public class RicercaContattiSpeciali { public static void main(String args[]) { System.out.println("Ricerca Contatti Speciali"); System.out.println(); var utente = new Utente(); System.out.println("Inserisci nome o parte del nome da ricercare"); Scanner scanner = new Scanner(System.in); String input = scanner.nextLine(); Speciale[] contattiSpecialiTrovati = utente.cercaContattiSpecialiPerNome(input); System.out.println("Contatti Speciali trovati con nome contenente \"" + input +"\"" ); for (Speciale speciale : contattiSpecialiTrovati) { if (speciale != null) { System.out.println(speciale+"\n"); } } } }
-
Esercizio 7.y)
Partendo dalla soluzione dell'esercizio precedente:
1. Aggiungere nella classeRubrica
un metodo chiamatogetContattiOrdinari
che restituisca tutti i contatti ordinari, ovvero che non sono di tipoSpeciale
.
2. Aggiungere un metodo chiamatocercaContattiOrdinariPerNome
nella classeUtente
, che cerca per nome solo i contatti che non sono speciali.
3. Creare una classeRicercaContattiOrdinari
omologa alle altre classi di ricerca che abbiamo già creato.
Soluzione
Potremmo scrivere il metodo
getContattiOrdinari
nella classeRubrica
nel seguente modo:
public Contatto[] getContattiOrdinari() { Contatto[] contattiOrdinariTrovati = new Contatto[contatti.length]; for (int i = 0, j = 0; i < contatti.length; ++i) { if (contatti[i] == null) { break; } if (!(contatti[i] instanceof Speciale)) { contattiOrdinariTrovati[j] = contatti[i]; j++; } } return contattiOrdinariTrovati; }
Mentre il metodocercaContattiOrdinariPerNome
nella classeUtente
potrebbe essere il seguente:
public Contatto[] cercaContattiOrdinariPerNome(String nome) { Contatto []contattiOrdinari = Rubrica.getInstance().getContattiOrdinari(); Contatto []contattiOrdinariTrovati = new Contatto[contattiOrdinari.length]; for (int i = 0, j = 0; i < contattiOrdinariTrovati.length; i++) { if (contattiOrdinari[i] == null) { break; } if (contattiOrdinari[i].getNome().toUpperCase(). contains(nome.toUpperCase())) { contattiOrdinariTrovati[j] = contattiOrdinari[i]; j++; } } return contattiOrdinariTrovati; }
Infine, potremmo implementare la classeRicercaContattiOrdinari
così:
package rubrica.interfaccia; import rubrica.dati.*; import rubrica.business.Utente; import java.util.Scanner; public class RicercaContattiOrdinari { public static void main(String args[]) { System.out.println("Ricerca Contatti Ordinari"); System.out.println(); var utente = new Utente(); System.out.println("Inserisci nome o parte del nome da ricercare"); Scanner scanner = new Scanner(System.in); String input = scanner.nextLine(); Contatto[] contattiTrovati = utente.cercaContattiOrdinariPerNome(input); System.out.println("Contatti ordinari trovati con nome contenente \"" + input +"\"" ); for (var contatto : contattiTrovati) { if (contatto != null) { System.out.println(contatto+"\n"); } } } }
-
Esercizio 7.z)
Partendo dalla soluzione dell'esercizio precedente:
1. Sino ad ora, abbiamo delegato alla classe che contengono il metodomain
la responsabilità di creare gli oggettiContatto
esplicitamente. Creiamo quindi una classeContattoFactory
che abbia questa responsabilità, dichiarando uno o più metodi staticigetContatto
, a cui vengono passati i campi necessari per istanziare gli oggetti.
2. Nella classeUtente
, aggiungere un metodo chiamato aggiungi che aggiunge un oggettoContatto
all'array contatti. Attenzione che nella classeRubrica
, l'array contatti può contenere un numero finito di oggetti di tipoContatto
. Aggiungere un numero di posti liberi per poter inserire nuovi oggetti.
3. Testare nella classeRicercaContatti
il corretto funzionamento del metodo aggiungi della classeRubrica
. Creare gli oggetti da passare al metodo sfruttando la classeContattoFactory
.
Soluzione
Potremmo implementare la classe
ContattoFactory
richiesta, creando usando un overload per il metodo chiamatogetContatto
:
package rubrica.dati; import rubrica.dati.Speciale; import rubrica.dati.Contatto; public class ContattoFactory { public static Contatto getContatto(String nome, String numeroDiTelefono, String indirizzo) { return new Contatto(nome, numeroDiTelefono, indirizzo); } public static Speciale getContatto(String nome, String numeroDiTelefono, String indirizzo, String suoneria) { return new Speciale(nome, numeroDiTelefono, indirizzo, suoneria); } }
Abbiamo modificato il costruttore della classeRubrica
nel seguente modo:
private Rubrica () { contatti = new Contatto[] { new Contatto("Claudio De Sio Cesari","Via Java 13","131313131313"), new Contatto("Stevie Wonder","Via Musica 10","1010101010"), new Contatto("Gennaro Capuozzo", "Piazza Quattro Giornate 1","1111111111"), new Speciale("Mario Ruoppolo","Via Neruda 3","333333","Il Postino"), new Speciale("Vincenzo Malinconico","Via dei Tribunali 8", "888888", "Tuca Tuca"), new Speciale("Logan Howlett","Piazza Canada 6","66666","Hurt"), null, null, null, null } ; }
Decidiamo di modificare la classeUtente
, non solo aggiungendo il metodo aggiungi richiesto, ma anche introducendo la variabile d'istanza rubrica, e un costruttore che la inizializza:
package rubrica.business; import rubrica.dati.*; public class Utente { private Rubrica rubrica; public Utente() { rubrica = Rubrica.getInstance(); } public Contatto[] cercaContattiPerNome(String nome) { Contatto []contatti = rubrica.getContatti(); Contatto []contattiTrovati = new Contatto[contatti.length]; for (int i = 0, j = 0; i < contattiTrovati.length; i++) { if (contatti[i] == null) { break; } if (contatti[i].getNome().toUpperCase(). contains(nome.toUpperCase())) { contattiTrovati[j] = contatti[i]; j++; } } return contattiTrovati; } public Speciale[] cercaContattiSpecialiPerNome(String nome) { Speciale []contattiSpeciali = rubrica.getContattiSpeciali(); Speciale []contattiSpecialiTrovati = new Speciale[contattiSpeciali.length]; for (int i = 0, j = 0; i < contattiSpecialiTrovati.length; i++) { if (contattiSpeciali[i] == null) { break; } if (contattiSpeciali[i].getNome().toUpperCase(). contains(nome.toUpperCase())) { contattiSpecialiTrovati[j] = contattiSpeciali[i]; j++; } } return contattiSpecialiTrovati; } public Contatto[] cercaContattiOrdinariPerNome(String nome) { Contatto []contattiOrdinari = rubrica.getContattiOrdinari(); Contatto []contattiOrdinariTrovati = new Contatto[contattiOrdinari.length]; for (int i = 0, j = 0; i < contattiOrdinariTrovati.length; i++) { if (contattiOrdinari[i] == null) { break; } if (contattiOrdinari[i].getNome().toUpperCase(). contains(nome.toUpperCase())) { contattiOrdinariTrovati[j] = contattiOrdinari[i]; j++; } } return contattiOrdinariTrovati; } public void inserisci(Contatto contattoDaAggiungere) { Contatto[] contatti = rubrica.getContatti(); for (int i = 0; i < contatti.length; ++i) { if (contatti[i] == null) { contatti[i] = contattoDaAggiungere; break; } } } }
Infatti, se non avessimo creato anche la variabile d'istanza e il costruttore avremmo potuto avere una situazione imprevista per quanto riguarda la classeRicercaContatti
, che abbiamo deciso di modifica nel seguente modo:
package rubrica.interfaccia; import rubrica.dati.*; import rubrica.business.Utente; import java.util.Scanner; public class RicercaContatti { public static void main(String args[]) { System.out.println("Ricerca Contatti"); System.out.println(); var utente = new Utente(); Contatto nuovoContatto = ContattoFactory.getContatto( "Molly Malone", "123456789", "Suffolk St, Dublin 2, D02 KX03, Ireland"); Speciale contattoSpeciale = ContattoFactory.getContatto( "Phil Lynott", "987654321", "Harry St, Dublin, Ireland", "Rosalie"); utente.inserisci(nuovoContatto); utente.inserisci(contattoSpeciale); System.out.println("Inserisci nome o parte del nome da ricercare"); Scanner scanner = new Scanner(System.in); String input = scanner.nextLine(); Contatto[] contattiTrovati = utente.cercaContattiPerNome(input); System.out.println("Contatti trovati con nome contenente \"" + input +"\"" ); for (Contatto contatto : contattiTrovati) { if (contatto != null) { System.out.println(contatto+"\n"); } } } }
Se non avessimo introdotto nella classeRubrica
la variabile e il costruttore, avremmo avuto che gli identificatori degli oggetti istanziati nel metodomain
nuovoContatto
econtattoSpeciale
, avrebbero avuto rispettivamente id =1 e id = 2. Infatti, il metododammiNumeroSeriale
della classeContatore
richiamata dal costruttore diContatto
, sarebbe stato chiamato prima per queste due istanze, mentre il metodogetInstance
della classeRubrica
, che a sua volta avrebbe inizializzato l'array contatti, sarebbe stato chiamato solo nel successivamente, e quindi avremmo avuto che gli id dei contatti sarebbero partiti dal numero 3.