Esercizi
- Home
- Esercizi Capitolo 9
Capitolo 9
Tipi Enumerazioni e Tipi Record
Il capitolo 9 è il primo dei capitoli della terza parte del libro "Caratteristiche avanzate". Gli
argomenti trattati in questo capitolo, non sono tutto sommato molto complessi. Ci sono alcune regole
particolari che bisogna comprendere, ma per la programmazione, l'importante è capire quando conviene
utilizzare record ed enumerazioni. Anche per questo capitolo troverete tanti esercizi a risposta
multipla (alcuni molto difficili) che supportano alla preparazione dell'esame di certificazione
Oracle, ma anche diverse implementazioni da codificare.
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 9.a) Record, Vero o Falso:
1. Un record viene trasformato in classe durante la compilazione. È possibile verificarlo mediante il tool
javap
fornito dal JDK.
2. Un record estende la classejava.util.Record
, e quindi non può estendere altre classi.
3. Non è possibile estendere un record, infatti i record sono dichiarati di defaultfinal
.
4. Un record non può implementare interfacce solo se sono di interfaccesealed
.
5. Un record implementa automaticamente i metodi setter e getter.
6. Possiamo sempre evitare di scrivere un record, implementando una classe equivalente che però richiederà un maggior sforzo implementativo a livello di righe di codice.
7. Possiamo sempre evitare di scrivere una classe, implementando una record equivalente che però richiederà un maggior sforzo implementativo a livello di righe di codice.
8. Nel momento in cui implementiamo un'interfaccia in un record, dovremo implementare obbligatoriamente tutti i metodi astratti ereditati.
9. Ogni record ha un costruttore di default.
10. Non è possibile creare un metodo setter per un record. Infatti, le variabili d'istanza sono sempre automaticamente dichiaratefinal
.
Soluzione
1. Vero.
2. Falso, un record estende la classejava.lang.Record
, non la classejava.util.Record
.
3. Vero.
4. Falso.
5. Falso, un record non implementa metodi setter.
6. Vero.
7. Falso, per esempio un record non può dichiarare metodi setter.
8. Vero.
9. Falso.
10. Vero. -
Esercizio 9.b) Enumerazioni e tipi innestati, Vero o Falso:
Per rispondere ad alcune domande del prossimo esercizio bisogna prima studiare l’approfondimento 9.5 sui tipi innestati (opzionale) che trovate nel file del materiale extra online.
1. Le enumerazioni non si possono istanziare se non all'interno della definizione dell'enumerazione stessa. Infatti, possono avere solamente costruttori private.
2. Un'enumerazione può dichiarare metodi, e i suoi elementi possono essere estesi da classi che possono sottoporre a override i metodi dell'enumerazione. Non è però possibile che una enum estenda un'altra enum.
3. Il metodo values appartiene ad ogni enumerazione ma non alla classejava.lang.Enum
.
4. Il seguente codice viene compilato senza errori:public enum MyEnum { public void metodo1() { } public void metodo2() { } ENUM1, ENUM2; }
5. Il seguente codice viene compilato senza errori:
public enum MyEnum { ENUM1 { public void metodo() { } }, ENUM2; public void metodo2() { } }
6. Il seguente codice viene compilato senza errori:
public enum MyEnum { ENUM1 (), ENUM2; private MyEnum(int i) { } }
7. Il seguente codice viene compilato senza errori:
public class Volume { public enum Livello { ALTO, MEDIO, BASSO }; // implementazione della classe . . . public static void main(String args[]) { switch (getLivello()) { case ALTO: System.out.println(Livello.ALTO); break; case MEDIO: System.out.println(Livello.MEDIO); break; case BASSO: System.out.println(Livello.BASSO); break; } } public static Livello getLivello() { return Livello.ALTO; } }
8. Se dichiariamo la seguente enumerazione:
public enum MyEnum { ENUM1 { public void metodo1() { } }, ENUM2 { public void metodo2() { } }; public void metodo1() { } }
il seguente codice potrebbe essere correttamente compilato:
MyEnum.ENUM1.metodo1();
9. Non è possibile dichiarare enumerazioni con un unico elemento.
10. Si possono innestare enumerazioni in enumerazioni in questo modo:public enum MyEnum { ENUM1 (), ENUM2; public enum MyEnum2 {a,b,c} }
ed il seguente codice viene compilato senza errori:
System.out.println(MyEnum.MyEnum2.a);
Soluzione
1. Vero.
2. Vero.
3. Falso.
4. Falso.
5. Vero.
6. Falso, non è possibile utilizzare il costruttore di default se ne viene dichiarato uno esplicitamente.
7. Vero.
8. Vero.
9. Falso.
10. Vero. -
Esercizio 9.c)
Modificare i sorgenti creati con gli esercizi realizzati per il capitolo 8, dopo aver sostituito l'interfaccia
TipoFile
, creata nell'esercizio 7.c, con un'enumerazione. Tutto dovrà funzionare come in precedenza.
Soluzione
Il listato della enumerazione
TipoFile
è molto semplice:
public enum TipoFile { JAVA, C_SHARP, C_PLUS_PLUS, C; }
Poi bisogna modificare la classeFile
, in modo tale da usare il tipoTipoFile
in luogo del vecchio tipo intero:
public abstract class File { private String nome; private TipoFile tipo; public File(String nome, TipoFile tipo) { this.nome = nome; this.tipo = tipo; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public TipoFile getTipo() { return tipo; } public void setTipo(TipoFile tipo) { this.tipo = tipo; } }
Poi bisogna modificare la classeFileSorgente
, in modo tale da usare il tipoTipoFile
in luogo del vecchio tipo intero:
public class FileSorgente extends File { private String contenuto; public FileSorgente(String nome, TipoFile tipo) { super(nome, tipo); } public FileSorgente(String nome, TipoFile tipo, String contenuto) { this(nome, tipo); this.contenuto = contenuto; } public void aggiungiTesto(String testo) { if (contenuto != null) { contenuto = ""; } if (testo != null) { contenuto += testo; } } 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); } } public String getContenuto() { return contenuto; } public void setContenuto(String contenuto) { this.contenuto = contenuto; } }
Tutto il resto può restare com'è. -
Esercizio 9.d)
Anche se ancora non solo introdotto nell'approfondimento 2.1 i tipi innestati (argomento che verrà ripreso solo nel capitolo 13), come tipi che si possono dichiarare all'interno di altri tipi, possiamo dire che è molto frequente dichiarare le enumerazioni, all'interno di classi. Questo perché spesso un'enumerazione viene pensata per un particolare contesto, e spesso deve essere utilizzata da un'unica classe.
Quindi, partendo dalla soluzione dell'esercizio precedente, innestare l'enumerazioneTipoFile
nella classeFile
. Che modifiche bisogna apportare all'applicazione per continuare a farla funzionare come prima?
Soluzione
Il listato di
File
con l'enumerazione innestataTipoFile
dovrebbe essere il seguente:
public abstract class File { public enum TipoFile { JAVA, C_SHARP, C_PLUS_PLUS, C; } private String nome; private TipoFile tipo; public File(String nome, TipoFile tipo) { this.nome = nome; this.tipo = tipo; } public String getNome() { return nome; } public void setNome(String nome) { this.nome = nome; } public TipoFile getTipo() { return tipo; } public void setTipo(TipoFile tipo) { this.tipo = tipo; } }
Poi dobbiamo modificare le due classi di test per referenziare correttamente l'elemento innestatoTipoFile
:
public class TestFileSorgente { public static void main(String args[]) { FileSorgente fileSorgente = new FileSorgente("Test.java", File.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()); } }
e:
public class TestIDE { public static void main(String args[]) { IDE ide = new JavaIDE(); FileSorgente fileSorgente = new FileSorgente("Test.java", File.TipoFile.JAVA, "public class MyClass {\n\r"); ide.modifica(fileSorgente, "}"); } }
Non è stato necessario invece modificare la classeFileSorgente
, perché essendo sottoclasse diFile
ha ereditato l'enumerazione. -
Esercizio 9.e)
La soluzione di questo esercizio riporta una sintassi complessa che non è stata riportata nelle pagine del libro cartaceo. In pratica, ogni singolo elemento dell'enumerazione può ridefinire al volo i metodi definiti nella enumerazione stessa. Questo significa che lo stesso metodo può avere un'implementazione diversa per ogni elemento. Quindi se non si riesca risolvere l'esercizio non c'è da preoccuparsi. Tale sintassi è usata raramente, e si è preferito non introdurla per non appesantire troppo l'argomento con caratteristiche meno usate. Ricordiamo che è previsto un volume complementare che dovrebbe essere pubblicato contemporaneamente alla release 17 di Java, che sarà dedicato alle novità introdotte dalla prossima versione LTS (cfr. appendice A), e alle caratteristiche avanzate.
Consideriamo i sorgenti creati con gli esercizi realizzati per il capitolo 5, e modificati con gli esercizi del capitolo 8. Attualmente, la classe
Moneta
potrebbe essere istanziata in maniera scorretta, e potrebbe essere necessario gestire eventuali eccezioni. Per esempio, come parametro al costruttore della classeMoneta
potrebbe essere passato un valore negativo. Segue il codice che abbiamo sino ad ora sviluppato (commenti omessi):
public class Moneta { public final static String VALUTA = "EURO"; private final int valore; public Moneta(int valore) { this.valore = valore; System.out.println("Creata una " + getDescrizione()); } public int getValore() { return valore; } public String getDescrizione() { String descrizione = "moneta da " + formattaStringaDescrittiva(valore) + VALUTA; return descrizione; } private String formattaStringaDescrittiva(int valore) { String stringaFormattata = " centesimi di "; if (valore == 1) { stringaFormattata = " centesimo di "; } else if (valore > 99) { stringaFormattata = " "; valore /= 100; } return valore + stringaFormattata; } }
Creare un'enumerazione che consenta di rendere il costruttore (e quindi l'intero programma) più robusto, e che ci permetta di evitare la gestione delle eccezioni. Riscrivere anche la classe
Moneta
.
Soluzione
Il listato della nostra enumerazione è abbastanza complesso:
public enum Valore { UN_CENTESIMO(1) { @Override public String getStringaDescrittiva() { return getValore() + " centesimi di "; } }, DUE_CENTESIMI(2), CINQUE_CENTESIMI(5), DIECI_CENTESIMI(10), VENTI_CENTESIMI(20), CINQUANTA_CENTESIMI(50), UN_EURO(1) { @Override public String getStringaDescrittiva() { return getValore() + " "; } }, DUE_EURO(2) { @Override public String getStringaDescrittiva() { return getValore() + " "; } }; private int valore; private Valore(int valore) { this.valore = valore; } public String getStringaDescrittiva() { return getValore() + " centesimi di "; } public int getValore() { return valore; } }
Abbiamo definito tutti i possibili valori per una moneta (supposta di tipo Euro). Ogni elemento dell'enumerazione usa un costruttore che imposta il valore numerico della variabilevalore
. Inoltre è definito il metodogetStringDescrittiva
che semplificherà la gestione fatta nella classeMoneta
originale. Tale metodo è sottoposto a override tramite una classe anonima per tre elementi dell'enumerazione. La classeMoneta
va modificata (semplificata) come segue:
public class Moneta { public final static String VALUTA = "EURO"; private final Valore valore; public Moneta(Valore valore) { this.valore = valore; System.out.println("Creata una " + getDescrizione()); } public Valore getValore() { return valore; } public String getDescrizione() { String descrizione = "moneta da " + valore.getStringaDescrittiva() + VALUTA; return descrizione; } }
-
Esercizio 9.f)
In base all'esercizio precedente, modificare la classe
PortaMonete
di conseguenza.
Soluzione
Il listato della classe
PortaMonete
va modificato nel seguente modo (commenti omessi):
public class PortaMonete { private static final int DIMENSIONE = 10; private final Moneta[] monete = new Moneta[DIMENSIONE]; public PortaMonete(Valore... valori) { assert monete.length == DIMENSIONE; try { int numeroMonete = valori.length; for (int i = 0; i < numeroMonete; i++) { if (i >= DIMENSIONE) { throw new PortaMonetePienoException( "Sono state inserite solo le prime + " + DIMENSIONE + " monete!"); } monete[i] = new Moneta(valori[i]); } // } catch (PortaMonetePienoException | NullPointerException exc) { } catch (PortaMonetePienoException exc) { System.out.println(exc.getMessage()); } catch (NullPointerException exc) { System.out.println("Il portamonete è stato creato vuoto"); } assert monete.length == DIMENSIONE; } public void aggiungi(Moneta moneta) throws PortaMonetePienoException { try { System.out.println("Proviamo ad aggiungere una " + moneta.getDescrizione()); } catch (NullPointerException exc) { throw new MonetaNullException(); } int indiceLibero = primoIndiceLibero(); if (indiceLibero == -1) { throw new PortaMonetePienoException("Portamonete pieno! La moneta " + moneta.getDescrizione() + " non è stata aggiunta..."); } else { monete[indiceLibero] = moneta; System.out.println("E' stata aggiunta una " + moneta.getDescrizione()); } } public Moneta preleva(Moneta moneta) throws MonetaNonTrovataException { try { System.out.println("Proviamo a prelevare una " + moneta.getDescrizione()); } catch (NullPointerException exc) { throw new MonetaNullException(); } Moneta monetaTrovata = null; int indiceMonetaTrovata = indiceMonetaTrovata(moneta); if (indiceMonetaTrovata == -1) { throw new MonetaNonTrovataException("Moneta non trovata!"); } else { monetaTrovata = moneta; monete[indiceMonetaTrovata] = null; System.out.println("Una " + moneta.getDescrizione() + " prelevata"); } return monetaTrovata; } public void stato() { System.out.println("Il portamonete contiene:"); for (Moneta moneta : monete) { if (moneta == null) { break; } System.out.println("Una " + moneta.getDescrizione()); } } private int primoIndiceLibero() { int indice = -1; for (int i = 0; i < 10; i++) { if (monete[i] == null) { indice = i; break; } } return indice; } private int indiceMonetaTrovata(Moneta moneta) { int indiceMonetaTrovata = -1; for (int i = 0; i < 10; i++) { if (monete[i] == null) { break; } Valore valoreMonetaNelPortaMoneta = monete[i].getValore(); Valore valore = moneta.getValore(); if (valore == valoreMonetaNelPortaMoneta) { indiceMonetaTrovata = i; break; } } return indiceMonetaTrovata; } }
-
Esercizio 9.g)
In base ai due ultimi esercizi, modificare la classe
TestMonete
di conseguenza.
Soluzione
Il listato della classe
TestMonete
è il seguente:
public class TestMonete { public static void main(String args[]) { Moneta monetaDaVentiCentesimi = new Moneta(Valore.VENTI_CENTESIMI); Moneta monetaDaUnCentesimo = new Moneta(Valore.UN_CENTESIMO); Moneta monetaDaUnEuro = new Moneta(Valore.UN_EURO); // Creaiamo un portamonete con 11 monete PortaMonete portaMoneteInsufficiente = new PortaMonete(Valore.DUE_CENTESIMI, Valore.CINQUE_CENTESIMI, Valore.UN_EURO, Valore.DIECI_CENTESIMI, Valore.CINQUANTA_CENTESIMI, Valore.DIECI_CENTESIMI, Valore.UN_EURO, Valore.DUE_EURO, Valore.DIECI_CENTESIMI, Valore.CINQUE_CENTESIMI, Valore.DUE_CENTESIMI); // Creaiamo un portamonete con 8 monete PortaMonete portaMonete = new PortaMonete(Valore.DUE_CENTESIMI, Valore.CINQUE_CENTESIMI, Valore.UN_EURO, Valore.DIECI_CENTESIMI, Valore.CINQUANTA_CENTESIMI, Valore.DIECI_CENTESIMI, Valore.UN_EURO, Valore.DUE_EURO); portaMonete.stato(); try { // Aggiungiamo una moneta da 20 centesimi portaMonete.aggiungi(monetaDaVentiCentesimi); } catch (PortaMonetePienoException | MonetaNullException exc) { System.out.println(exc.getMessage()); } try { // Aggiungiamo la decima moneta da 1 centesimo. portaMonete.aggiungi(monetaDaUnCentesimo); } catch (PortaMonetePienoException | MonetaNullException exc) { System.out.println(exc.getMessage()); } try { // Aggiungiamo l'undicesima moneta (dovremmo ottenere un errore e //la moneta non sarà aggiunta) portaMonete.aggiungi(monetaDaUnEuro); } catch (PortaMonetePienoException | MonetaNullException exc) { System.out.println(exc.getMessage()); }// Valutiamo lo stato del portamonete. portaMonete.stato(); try { // preleviamo 20 centesimi portaMonete.preleva(monetaDaVentiCentesimi); } catch (MonetaNonTrovataException exc) { System.out.println(exc.getMessage()); } try { // Aggiungiamo l'undicesima moneta (dovremmo ottenere un //errore e la moneta non sarà aggiunta) portaMonete.aggiungi(monetaDaUnEuro); } catch (PortaMonetePienoException | MonetaNullException exc) { System.out.println(exc.getMessage()); } portaMonete.stato(); //il prossimo esempio non è più riproducibile // try { // //Cerchiamo una moneta non esistente (dovremmo ottenere una stampa di errore) // // portaMonete.preleva(new Moneta(7)); // } catch (MonetaNonTrovataException exc) { // System.out.println(exc.getMessage()); // } //testiamo il passaggio di null al costruttore del porta monete PortaMonete portaMoneteEccezione = new PortaMonete(null); portaMonete.stato(); try { // proviamo ad aggiungere null portaMonete.aggiungi(null); } catch (PortaMonetePienoException | MonetaNullException exc) { System.out.println(exc.getMessage()); } try { //Proviamo a prelevare una moneta null portaMonete.preleva(null); } catch (MonetaNonTrovataException | MonetaNullException exc) { System.out.println(exc.getMessage()); } } }
-
Esercizio 9.h)
Consideriamo la classe
PortaMonete
rifinita nell'esercizio 9.f, e concentriamoci sul metodo privatoindiceMonetaTrovata
, che abbiamo definito nel seguente modo:
private int indiceMonetaTrovata(Moneta moneta) { int indiceMonetaTrovata = -1; final int size = monete.size(); for (int i = 0; i < size; i++) { if (monete.get(i) == null) { break; } Valore valoreMonetaNelPortaMoneta = monete.get(i).getValore(); Valore valore = moneta.getValore(); if (valore == valoreMonetaNelPortaMoneta) { indiceMonetaTrovata = i; break; } } return indiceMonetaTrovata; }
Creare un metodo
equals
nella classe Moneta, in modo tale da semplificare la ricerca in questo metodo.
Soluzione
Il listato del metodo
equals
l'abbiamo generato (insieme al metodohashcode
che riportiamo per completezza anche se non necessario ai fini dell'esercizio) tramite Netbeans (abbiamo anche utilizzato la classejava.util.Objects
che spiegheremo più avanti nel libro):
@Override public boolean equals(Object obj) { if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } final Moneta other = (Moneta) obj; if (this.valore != other.valore) { return false; } return true; } @Override public int hashCode() { int hash = 7; hash = 97 * hash + Objects.hashCode(this.valore); return hash; }
Quindi il metodoindiceMonetaTrovata
si può semplificare nel seguente modo:
private int indiceMonetaTrovata(Moneta moneta) { int indiceMonetaTrovata = -1; final int size = monete.size(); for (int i = 0; i < size; i++) { if (monete.get(i) == null) { break; } if (monete.get(i).equals(moneta)) { indiceMonetaTrovata = i; break; } } return indiceMonetaTrovata; }
-
Esercizio 9.i)
Riscrivere il metodo
toString
del seguente recordPersona
:
public record Persona(String nome, String cognome, String dataDiNascita, String professione, String indirizzo) {}
il nostro requisito è che il metodo
toString
restituisca una stringa con il seguente formato:
Nome: Arjen Anthony Cognome: Lucassen Professione: Compositore Data di Nascita: 03/04/1960 Indirizzo: Olanda
Per la formattazione è possibile sfruttare il carattere di escape\t
introdotto nel capitolo 3.
Soluzione
La soluzione potrebbe essere la seguente:
public record Persona(String nome, String cognome, String dataDiNascita, String professione, String indirizzo) { @Override public String toString() { String string = "Nome: \t\t\t" + nome(); string += "\nCognome: \t\t" + cognome(); string += "\nProfessione: \t\t" + professione(); string += "\nData di Nascita: \t" + dataDiNascita(); string += "\nIndirizzo: \t\t" + indirizzo(); return string; } }
-
Esercizio 9.j)
Qual è l'output della seguente classe?
public class Esercizio9J { public static void main(String args[]) { String stringa1 ="Claudio"; String stringa2 = new String(stringa1); System.out.println(stringa2 == stringa1); System.out.println(stringa2.equals(stringa1)); System.out.println("Claudio".equals(stringa1)); System.out.println("Claudio" == stringa1); System.out.println("Claudio" == stringa2); } }
Soluzione
L'output della classe è:
false true true true false
Infattistringa1
estringa2
sono due stringhe diverse, quindi è ovvio che la prima istruzione di stampa, che confronta le stringhe con l'operatore==
che si basa sugli indirizzi dei reference, stampifalse
. La seconda e la terza istruzione di stampa invece stamperannotrue
, perché il metodoequals
confronta i contenuti delle stringhe e non gli indirizzi dei reference. Il risultato della quarta e la quinta istruzioni di stampa, invece dipende dal fatto che la stringaClaudio
, è una stringa che è stata messa nella pool di stringhe (cfr. paragrafo F.4.1 dell'appendice F) e il cui indirizzo coincide con l'indirizzo distringa1
(vedi la prima istruzione del metodo) ma non con quella distringa2
. -
Esercizio 9.k) Approfondimento import statici, Vero o Falso:
1. Gli
import
statici permettono di non referenziare i membri statici importati.
2. Non è possibile, dopo avere importato staticamente una variabile, referenziarla all'interno del codice.
3. La seguente importazione non è corretta perchéjava.lang
è sempre importato implicitamente:import static java.lang.System.out;
4. In alcuni casi gli
5. Considerando la seguente enumerazione:import
statici potrebbero peggiorare la leggibilità dei nostri file.il seguente codice è compilabile correttamente:package mypackage; public enum MyEnum { A,B,C }
import static mypackage.MyEnum.*; public class MyClass { public MyClass(){ out.println(A); } }
6. Se utilizziamo gli
import
statici, si potrebbero importare anche due membri statici con lo stesso nome. Il loro utilizzo all'interno del codice darebbe luogo ad errori in compilazione, se non referenziati.
7. Lo shadowing è un fenomeno che potrebbe verificarsi se si utilizzano gliimport
statici.
8. Essenzialmente l'utilità degliimport
statici risiede nella possibilità di scrivere meno codice probabilmente superfluo.
9. Non ha senso importare staticamente una variabile, se poi viene utilizzata una sola volta all'interno del codice.
10. Non è consigliato utilizzare gliimport
statici per importare gli elementi di enumerazioni.
Soluzione
1. Vero.
2. Falso.
3. Falso,out
non è importato staticamente.
4. Vero.
5. Vero.
6. Vero.
7. Vero.
8. Vero.
9. Vero.
10. Falso. -
Esercizio 9.q)
Per rispondere ad alcune domande del prossimo esercizio bisogna prima studiare l’approfondimento 9.5 sui tipi innestati (opzionale) che trovate nel file del materiale extra online.
Dato il seguente codice:
public enum Esercizio9Q { A, B, C; private enum InnerEnum { C, D, E; protected enum InnerInnerEnum { F,G,H } } public static void main(String args[]) { System.out.println(/*INSERISCI CODICE QUI*/); } }
Quali delle seguenti espressioni è possibile inserire al posto del commento
/*INSERISCI CODICE QUI*/
per stampare il valoreH
(è possibile scegliere zero o più espressioni):
1.Esercizio9Q.A.InnerEnum.C.InnerInnerEnum.H
2.Esercizio9Q.InnerEnum.InnerInnerEnum.H
3.InnerInnerEnum.H
4.InnerEnum.A.InnerInnerEnum.H
5.InnerEnum.InnerInnerEnum.H
6.InnerEnum.InnerInnerEnum.F.G.H
7.Esercizio9Q.A.C.H
8. Nessuno: il codice non compila.
Soluzione
Le risposte corrette sono le 2, 5 e persino la 6.
-
Esercizio 9.r)
Per rispondere ad alcune domande del prossimo esercizio bisogna prima studiare l’approfondimento 9.5 sui tipi innestati (opzionale) che trovate nel file del materiale extra online.
Dato il seguente codice:
public enum Esercizio9R implements Interface { UNO { @Override public int metodo() { return 29 + 7 + 74; } }, DUE, TRE{ @Override public int metodo() { return 12 + 11 + 6; } }; @Override public int metodo() { return 14 + 4 + 4; } public static void main(String args[]) { Interface i = Esercizio9R.TRE; System.out.println(i.metodo()); } } interface Interface { int metodo(); }
Se viene eseguito il file
Esercizio9R
, cosa verrà stampato (scegliere una sola risposta)?1.22
2.29
3.110
4.161
5.TRE
6.TRE.metodo()
7.i.metodo()
8. Niente, il codice non compila.
Soluzione
La risposta giusta è la 2, ovvero viene stampato
29
. Infatti il metodometodo
, ereditato dall'interfacciaInnerInterface
, è riscritto dall'enumerazioneEsercizio9R
con un'implementazione che restituisce il numero22
. Ma per lo specifico elemento dell'enumerazioneTRE
, il metodo viene nuovamente riscritto in modo tale da restituire appunto il valore29
. Nel metodomain
l'elementoEsercizio9R.TRE
viene assegnato al referencei
dell'interfacciaInterface
(è legale per il polimorfismo per dati). Poi viene invocato virtualmente il metodo metodo sui
, che in realtà punta all'elementoTRE
, che come abbiamo detto, ha ridefinito un'implementazione del metodometodo
, che restituisce29
. -
Esercizio 9.v)
Astrarre un mazzo di carte napoletane e creare una classe eseguibile (con metodo
main
) che istanzi tutte le 40 carte.
Soluzione
Abbiamo deciso di utilizzare le enumerazioni sia per astrarre il concetto di seme che quello del numero delle carte. Un numero potrebbe essere rappresentato con un tipo primitivo come un
int
, o anche unbyte
. Ma tutto sommato per il nostro scopo l'enumerazione è parsa la scelta migliore per poter rappresentare anche testualmente le varie carte. Di seguito il codice dell'enumerazioneNumero
:
public enum Numero { UNO("Asso"), DUE("Due"), TRE("Tre"), QUATTRO("Quattro"), CINQUE("Cinque"), SEI("Sei"), SETTE("Sette"), OTTO("Otto"), NOVE("Nove"), DIECI("Dieci"); String rappresentazione; Numero(String rappresentazione) { this.rappresentazione = rappresentazione; } @Override public String toString() { return rappresentazione; } }
Si noti che abbiamo sfruttato la variabilerappresentazione
per gestire la rappresentazione testuale degli elementi dell'enumerazione. Questa viene impostata nel momento della definizione sfruttando l'unico costruttore fornito. Con questa variabile abbiamo potuto far stampare la stringaAsso
in luogo diUno
, in modo piuttosto semplice (ovvero senza utilizzare particolari algoritmi che usano condizioni comeswitch
oif
). Se avessimo utilizzato un tipo primitivo comeint
invece avremmo dovuto gestire la situazione con un algoritmo, che implica maggiori probabilità di errore. Si noti che abbiamo anche ridefinito il metodotoString
ereditato dalla classeObject
per facilitare la stampa degli elementi dell'enumerazione.
Per essere sicuri di aver scritto del codice corretto, abbiamo anche creato una classe di test, praticamente uno unit test (cfr. approfondimento 8.2) artigianale e semplificato per testare solo la stampa degli elementi dell'enumerazione:
/** * Classe che testa l'enumerazione Numero. */ public class TestNumero { public static void main(String args[]) { for (Object object : Numero.values()) { System.out.println(object); } } }
Il cui output ha verificato la correttezza del codice (secondo le nostre intenzioni):
Asso Due Tre Quattro Cinque Sei Sette Otto Nove Dieci
Passiamo all'enumerazione che rappresenta ilSeme
di una carta:
public enum Seme { COPPE, BASTONI, DENARI, SPADE; public String toString() { return rendiMaiuscolo(this.name()); } private String rendiMaiuscolo(String stringa) { //rendiamo minuscola la stringa String minuscolo = stringa.toLowerCase(); //recuperiamo la prima lettera della stringa String iniziale = minuscolo.substring(0,1); //rendiamo maiuscola la prima lettera String inizialeMaiuscola = iniziale.toUpperCase(); //ritorniamo la concatenazione tra la lettera maiuscola //e il resto della stringa minuscola return inizialeMaiuscola + minuscolo.substring(1); } }
In questo caso, per scopi didattici, abbiamo modificato il modo in cui vengono rappresentati gli elementi dell'enumerazione. Abbiamo realizzato un metodo d'utilità chiamatorendiMaiuscolo
, che rende maiuscola la stringa passata in input. Le istruzioni di tale metodo sono commentate e facilmente comprensibili. Il metodotoString
, non fa altro che restituire la stringa maiuscola del nome dell'elemento dell'enumerazione. Anche in questo caso abbiamo creato una classe di test:
/** * Classe che testa l'enumerazione Seme. */ public class TestSeme { public static void main(String args[]) { for (Object object : Seme.values()) { System.out.println(object); } } }
che una volta eseguita produce l'output voluto:
Coppe Bastoni Denari Spade
La classeCarta
è molta semplice:
public class Carta { private Seme seme; private Numero numero; public Carta (Numero numero, Seme seme) { this.numero = numero; this.seme = seme; } public void setNumero(Numero numero) { this.numero = numero; } public Numero getNumero() { return numero; } public void setSeme(Seme seme) { this.seme = seme; } public Seme getSeme() { return seme; } public String toString() { return numero + " di " + seme; } }
La classe più complicata è indubbiamente la classeMazzoDiCarte
, che abbiamo voluto astrarre con un array bidimensionale di 4 righe (una per ogni seme) e 10 colonne (una per ogni numero):
public class MazzoDiCarte { private Carta[][] carte; public MazzoDiCarte() { carte = new Carta[4][10]; caricaCarte(); } public void caricaCarte() { Seme[] semi = Seme.values(); Numero[] numeri = Numero.values(); int semiLength = semi.length; int numeriLength = numeri.length; for (int i = 0; i < semiLength; i++) { for (int j = 0; j < numeriLength; j++) { carte[i][j] = new Carta(numeri[j], semi[i]); } } } public String toString() { String string = ""; for (int i = 0; i < carte.length; i++) { for (int j = 0; j < carte[i].length; j++) { string += carte[i][j] + ", "; } string += "\n"; } return string; } public void setCarte(Carta[][] carte) { this.carte = carte; } public Carta[][] getCarte() { return carte; } }
Si noti che il costruttore istanzia l'unica variabile d'istanza (ovvero l'array bidimensionalicarte
) e poi invoca il metodocaricaCarte
, che si occupa di caricare nell'array tutte le carte del mazzo con un doppio ciclofor
. Il metodotoString
invece crea e restituisce la stringa rappresentativa del mazzo di carte.
Infine, la classe principale (quella con il metodomain
) è la seguente:
public class Esercizio9V { public static void main(String args[]) { MazzoDiCarte mazzoDiCarte = new MazzoDiCarte(); System.out.println(mazzoDiCarte); } }
Dove con un doppio ciclo abbiamo stampato le quaranta carte del mazzo.
L'output è il seguente (purtroppo la pagina è troppo stretta rispetto ad ogni riga da stampare, quindi l'output risulta un po' sfalsato):
Asso di Coppe, Due di Coppe, Tre di Coppe, Quattro di Coppe, Cinque di Coppe, Sei di Coppe, Sette di Coppe, Otto di Coppe, Nove di Coppe, Dieci di Coppe, Asso di Bastoni, Due di Bastoni, Tre di Bastoni, Quattro di Bastoni, Cinque di Bastoni, Sei di Bastoni, Sette di Bastoni, Otto di Bastoni, Nove di Bastoni, Dieci di Bastoni, Asso di Denari, Due di Denari, Tre di Denari, Quattro di Denari, Cinque di Denari, Sei di Denari, Sette di Denari, Otto di Denari, Nove di Denari, Dieci di Denari, Asso di Spade, Due di Spade, Tre di Spade, Quattro di Spade, Cinque di Spade, Sei di Spade, Sette di Spade, Otto di Spade, Nove di Spade, Dieci di Spade,
Si noti anche che la formattazione è tutt'altro che perfetta. Cercheremo di risolvere il problema nel prossimo esercizio. -
Esercizio 9.z)
Partendo dalla soluzione dell'esercizio precedente:
• Individuare i problemi di formattazione.
• Risolvere i problemi di formattazione.Questo esercizio, testa la vostra capacità di risolvere i problemi con inventiva, coscienza e intraprendenza. Praticamente quello che ogni programmatore dovrebbe fare sempre.
Soluzione
I problemi di formattazione sono:
• Alla fine di ogni riga viene stampato una virgola e uno spazio non necessari.Risolviamo i due problemi con due controlli all'interno dei due cicli del metodo, uno con un operatore ternario che aggiunge una virgola e uno spazio solo se non siamo all'ultima iterazione sui numeri, e uno con un
• Alla fine della stringa vengono stampati due a capo non necessari.if
che aggiunge un "a capo" solo se non siamo all'ultima iterazione dei semi:
//... public String toString() { int carteLength = carte.length; String string = ""; for (int i = 0; i < carteLength; i++) { int rigaCarteLength = carte[i].length; for (int j = 0; j < rigaCarteLength; j++) { string += carte[i][j] + (j != (rigaCarteLength-1) ? ", " : ""); } if (i != (carteLength-1)) { string += "\n"; } } return string; } //...