mattone
dopo mattoneLA COLLANA
IT/EN
← Guida Java

Java 25 · 31/39

Attività e soluzioni ragionate C01-C06

Java 25 · Guida completa · Bozza in revisione

Questa guida conserva lo stato di revisione del libro. La verifica editoriale e le prove di comprensione con lettori indipendenti sono ancora da completare.

Cerca in tutta la guida →

Queste sono le prime attività collegate alle bozze dei capitoli. Ogni consegna dichiara che cosa osservare; le soluzioni sono esempi motivati, da confrontare con il proprio tentativo. Tutto il codice del percorso principale usa Java 25 senza preview. Per compilare i sorgenti e controllare gli output in un ambiente pulito si può eseguire verifica-primi-sei.py dalla radice del progetto.

C01-A01 – Scegliere il percorso e verificare l'ambiente

Consegna. Scrivi in tre frasi che cosa conosci già di programmazione, quale sezione del libro leggerai per prima e quale risultato ti farà dire che hai capito il capitolo. Poi esegui java --version e javac --version e conserva i due output insieme alla data. Non inserire percorsi personali o dati riservati nella consegna.

Criterio. Il percorso scelto deve essere compatibile con i prerequisiti dichiarati. Entrambi i comandi devono essere disponibili e mostrare una versione coerente del JDK. L'attività non assegna un voto alle conoscenze di partenza: serve a scegliere da dove cominciare.

Suggerimenti. Se non sai scegliere, parti dai capitoli C02 e C03 e rimanda i box di approfondimento. Se un comando non viene trovato, distingui il JDK assente dal percorso degli eseguibili non configurato. Se le versioni differiscono, controlla quale installazione viene trovata per prima dal terminale.

Soluzione commentata. Una risposta possibile è: «Conosco variabili e cicli, ma non ho mai usato Java. Leggo C02-C05 in ordine e provo ogni programma completo. Considero capito C03 quando so spiegare perché javac e java sono comandi diversi». Gli output dipendono dal computer, quindi non esiste una stringa da copiare. La prova osservabile è poter ripetere i due comandi e spiegare che il secondo verifica il compilatore.

C01-G01 – Usare le convenzioni del libro

Consegna. Apri la sezione sulle convenzioni e scegli un programma completo, un frammento di codice, una nota e un approfondimento nei capitoli C02-C06. Per ciascuno scrivi quale operazione ti è richiesta: compilarlo, inserirlo in un contesto, usarlo per evitare un errore o rimandarlo dopo il percorso principale. Spiega anche dove cercheresti il sorgente di un programma completo.

Criterio. La risposta distingue un programma avviabile da un frammento e non tratta note e approfondimenti come istruzioni da copiare nel terminale. Il sorgente completo viene ritrovato nei materiali del capitolo.

Suggerimenti. Cerca un blocco che dichiara public class e uno che mostra soltanto poche righe. Leggi il paragrafo prima e dopo ciascun blocco.

Soluzione commentata. Un programma completo si salva nel file indicato e si verifica con i comandi riportati; un frammento richiede il contesto di una classe o di un metodo. Una nota richiama un rischio concreto, mentre un approfondimento aggiunge un dettaglio che si può leggere dopo il percorso principale. I file in esempi sono la versione da compilare dei programmi completi.

C02-A01 – Riconoscere responsabilità e relazioni

Consegna. Un lettore prende in prestito un libro. Indica in quale oggetto registreresti la data del prestito e perché. Poi completa a parole la differenza fra «Biblioteca è una Collezione» e «Biblioteca ha una collezione». Non serve scrivere codice.

Criterio e feedback. La risposta deve collegare la data all'evento del prestito e distinguere ereditarietà da composizione. Se la data finisce nel solo Libro, chiediti come rappresenteresti due prestiti dello stesso libro in momenti diversi. Se usi «è un» soltanto per riusare metodi, rileggi il paragrafo sulle responsabilità.

Soluzione commentata. Prestito collega un libro a un lettore in un intervallo di tempo; perciò conosce la data iniziale. Dire «Biblioteca ha una collezione» descrive una parte del suo stato, mentre «Biblioteca è una Collezione» attribuisce alla biblioteca il tipo e il contratto di una collezione. La seconda frase richiede una giustificazione più forte del semplice riuso.

C02-P01 – Modellare una biblioteca

Consegna. A partire da un lettore che chiede un libro e lo restituisce, proponi almeno tre entità del programma. Per ciascuna indica una responsabilità, un dato che conosce e un'operazione che compie. Disegna le relazioni fra le entità. Poi valuta la frase «La biblioteca estende la collezione di libri perché così riusa i metodi della collezione».

Criterio. Il modello deve distinguere Libro, Lettore e Prestito; una relazione di ereditarietà deve significare davvero «è un». Una regola deve impedire uno stato impossibile, per esempio due restituzioni dello stesso prestito senza una decisione esplicita. La motivazione vale più di una gerarchia unica.

Suggerimenti. Chiediti a quale evento appartiene una data di restituzione. Prova a leggere ad alta voce le relazioni come frasi. Se «Biblioteca è una Collezione» sembra forzato, valuta «Biblioteca ha una collezione».

Soluzione commentata. Libro conosce titolo e autore; Lettore identifica chi prende in prestito; Prestito collega i due e conosce inizio ed eventuale restituzione. La biblioteca coordina ricerca e registrazione e ha una collezione di libri. L'ereditarietà proposta nella consegna confonde riuso del codice e appartenenza concettuale. Una verifica umana applica la rubrica del piano delle esercitazioni: correttezza, motivazione, qualità del modello e robustezza delle regole.

C02-D01 – Una gerarchia costruita per riusare codice

Consegna. Un progetto propone Prestito extends Libro perché entrambi hanno una data, e Biblioteca extends ArrayList<Libro> perché la biblioteca conserva libri. Individua i due rapporti concettualmente fragili e riscrivili in prosa senza usare ereditarietà dove non c'è un rapporto «è un».

Criterio. La diagnosi distingue il prestito dal libro e la biblioteca dalla collezione che usa. Non basta dire che extends è sintatticamente possibile: occorre spiegare quali promesse farebbe ai chiamanti.

Suggerimenti. Leggi ad alta voce «un prestito è un libro» e «una biblioteca è una lista». Chiediti dove appartiene la data di restituzione.

Soluzione commentata. Prestito collega libro e lettore e conosce le date dell'operazione; ha un riferimento al libro. Biblioteca coordina un catalogo e ha una collezione, ma non deve esporre automaticamente tutte le operazioni di ArrayList. La composizione rende più chiari i due contratti.

C03-A01 – Sorgente, classe, esecuzione

Consegna. Metti nell'ordine di lavoro Saluto.java, javac, Saluto.class, java e JVM. Spiega poi perché cambiare soltanto il file .java non modifica l'output del programma già compilato.

Criterio e feedback. La spiegazione deve distinguere il file sorgente dal file compilato e il compilatore dal programma di avvio. Se attribuisci a java la produzione del .class nell'esempio del capitolo, ripercorri i due comandi stampati in C03.

Soluzione commentata. javac legge Saluto.java e produce Saluto.class; il comando java avvia la JVM, che carica la classe e ne esegue il metodo main. Una modifica al sorgente ha effetto su quel file, ma non riscrive da sola il .class già presente.

C03-G01 – Compilare, osservare, diagnosticare

Consegna. Entra nella cartella che contiene Saluto.java e Argomenti.java, poi compila il primo con javac --release 25 -d build Saluto.java. Avvialo con java -cp build Saluto e osserva la struttura della classe con javap -classpath build Saluto. Modifica soltanto la frase nel sorgente e riesegui senza ricompilare; infine ricompila e riprova. Compila anche Argomenti.java con javac --release 25 -d build Argomenti.java ed esegui java -cp build Argomenti primo. Spiega quali parti della riga di comando sono opzioni del comando java e quale parte è l'argomento del programma.

Criterio. L'output dopo la prima compilazione è Ciao, Java 25!. javap mostra il metodo main. La modifica al solo .java non cambia il comportamento del .class già prodotto; la ricompilazione lo aggiorna. Il secondo programma stampa Primo argomento: primo. Registra comandi, versione JDK e codici di uscita.

Suggerimenti. Se java non trova la classe, verifica la cartella passata a -cp. Se javac non trova il sorgente, verifica la cartella da cui lanci il comando. Se modifichi una parola chiave e il compilatore fallisce, sei ancora nella fase di compilazione: la JVM non ha eseguito il nuovo programma.

Soluzione commentata. javac legge il testo e produce build/Saluto.class; java legge il file compilato nella cartella indicata. Per questo un cambiamento nel sorgente diventa osservabile soltanto dopo un'altra compilazione. javap non ricostruisce il testo originale, ma mostra la struttura della classe compilata e rende visibile la presenza di main. Nella seconda esecuzione -cp build è un'opzione di java, Argomenti è il nome della classe da avviare e primo è il testo consegnato al suo metodo main.

C03-D01 – Un file compilato che non viene trovato

Consegna. In una cartella pulita compila Saluto.java con javac --release 25 -d build Saluto.java. Se lanci java -cp . Saluto dalla cartella che contiene solo il sorgente e build/, il programma non trova la classe. Spiega perché e correggi il comando senza ricompilare.

Criterio. La risposta indica che il file Saluto.class è in build/ e usa java -cp build Saluto. Non attribuisce l'errore al contenuto del metodo main.

Suggerimenti. Osserva l'effetto dell'opzione -d build e il ruolo di -cp nel comando di avvio.

Soluzione commentata. javac ha prodotto build/Saluto.class; -cp . cerca la classe nella cartella corrente e non dentro build/. java -cp build Saluto indica dove trovare la classe già compilata. Non serve cambiare il sorgente.

C04-A01 – Prevedere tipo e indice

Consegna. Senza eseguire codice, indica il primo indice valido di int[] voti = {20, 25, 30};, il valore di voti.length e il tipo dell'espressione a + b quando a e b sono variabili byte. Spiega in una frase perché var non rende dinamico il tipo di una variabile locale.

Criterio e feedback. Le tre risposte sono 0, 3 e int. Se confondi lunghezza e ultimo indice, disegna tre caselle e scrivi sopra 0, 1, 2. Se pensi che var permetta di cambiare tipo in seguito, rileggi la distinzione fra tipo inferito e tipo effettivo.

Soluzione commentata. Gli indici dell'array partono da zero, quindi la terza casella ha indice 2 pur essendoci tre elementi. Nell'addizione i due byte sono promossi a int. Con var il compilatore ricava un tipo dall'inizializzatore; quel tipo resta quello della variabile per tutta la sua durata.

C04-P01 – Quattro voti e una conversione

Consegna. Modifica MediaVoti.java usando {20, 25, 28, 30} e calcola la media. Prima di eseguire, prevedi il risultato. Poi spiega che cosa cambierebbe togliendo (double) e perché byte c = a + b; può non compilare anche quando a e b sono piccoli. Infine esegui ConversioniNumeriche.java e confronta l'output con la tua previsione.

Criterio. La media è 25.75; la somma include quattro elementi. La spiegazione distingue divisione intera, conversione esplicita e promozione di byte a int. I valori osservati in ConversioniNumeriche sono confrontati con quelli attesi nel capitolo.

Suggerimenti. Somma prima i quattro numeri su carta. Controlla il tipo dell'operando sinistro di /. Ricorda che il compilatore decide il tipo di a + b usando le regole dell'espressione, non la grandezza dei valori in un'esecuzione particolare.

Soluzione commentata. 20 + 25 + 28 + 30 vale 103; 103 / 4.0 vale 25.75. Senza il cast, 103 / 4 produce l'intero 25, che viene poi convertito in 25.0 se assegnato a un double. Un'espressione aritmetica con due byte viene promossa a int, quindi l'assegnazione diretta a byte richiede un controllo o una conversione, che può perdere informazione. Nel programma sulle conversioni Long.MAX_VALUE ristretto a int diventa -1; l'allargamento di Integer.MAX_VALUE a long conserva il valore.

C04-D01 – Un indice oltre l'ultimo elemento

Consegna. Valuta il frammento int[] voti = {20, 25, 30}; for (int i = 0; i <= voti.length; i++) System.out.println(voti[i]);. Indica le righe che compaiono prima dell'errore, il primo indice non valido e la correzione minima della condizione del ciclo.

Criterio. Sono stampati 20, 25 e 30; l'accesso con indice 3 fallisce durante l'esecuzione. La condizione diventa i < voti.length.

Suggerimenti. Scrivi gli indici sotto i tre elementi. Il valore di length conta le caselle ma non è un indice valido in questo array.

Soluzione commentata. Gli indici validi sono 0, 1 e 2. Con <=, il ciclo prova anche i == 3; l'array non ha quella casella e Java lancia ArrayIndexOutOfBoundsException. Con < il ciclo si ferma dopo l'ultimo elemento.

C04-P02 – Rappresentazione, tipo e copia

Consegna. Prima di eseguire BitPassoPasso.java, prevedi i risultati di 100 >> 1, 100 ^ 125, ~100 e -8 >>> 1. Indica quali risultati si capiscono osservando soltanto gli otto bit finali e quali richiedono di ricordare che il tipo è int. Poi crea int[] primo = {24, 27}; int[] secondo = primo;, assegna 30 a secondo[0] e prevedi il valore di primo[0]. Ripeti dopo aver ottenuto secondo con primo.clone().

Criterio e feedback. I quattro valori sono 50, 25, -101 e 2_147_483_644; ~ e >>> sul valore negativo richiedono la rappresentazione completa dell'int. Nel primo caso i due nomi indicano lo stesso array e primo[0] diventa 30; dopo la copia resta 24.

Soluzione commentata. Lo shift destro di 100 produce 50; XOR conserva i bit diversi di 100 e 125, ottenendo 25. ~ inverte tutti i 32 bit e >>> inserisce zeri a sinistra, perciò gli otto bit disegnati sul foglio non bastano per i due risultati. L'assegnazione di un riferimento a un altro non copia gli elementi; clone() crea qui un nuovo array di valori primitivi indipendente dal primo.

C05-A01 – Scelta o ripetizione?

Consegna. Scegli una struttura di controllo per ciascun caso e spiega la scelta: assegnare un'etichetta a un numero da 1 a 7; continuare a chiedere un dato finché non è valido; visitare ogni voto di un array. Per il primo caso indica anche dove metteresti il valore da usare quando il numero non è nell'intervallo.

Criterio e feedback. Devono comparire una scelta con default, un ciclo e una visita dell'array. Se usi if per ogni voto già noto nell'array, chiediti quale parte del codice cambierebbe quando i voti diventano cento.

Soluzione commentata. Una espressione switch rende visibili i sette casi e un default copre gli altri interi. Un while ripete la richiesta finché la condizione resta vera; la condizione va definita in modo che il ciclo possa terminare. Un for esteso visita i voti quando non serve conoscere l'indice.

C05-G01 – Dalla scelta ai cicli

Consegna. Esegui MediaVotiConCiclo.java con i tre voti iniziali {24, 27, 30}, poi con {20, 25, 28, 30} e infine con un array vuoto. Modifica l'array nel sorgente e ricompila fra una prova e l'altra; prima di eseguire, predici ogni output. Poi osserva DamaIniziale.java: spiega perché produce dodici pedine per colore e quali due righe della tavola restano vuote. Riscrivi infine una classificazione di giorno feriale/fine settimana con un'espressione switch, usando i casi 1-7 e un default per gli altri interi.

Criterio. Nessun caso vuoto provoca una divisione; la media non dipende dalla lunghezza fissata nel sorgente; lo switch produce un valore per ogni intero; la tavola conta dodici pedine bianche e dodici nere.

Suggerimenti. La guardia dell'array vuoto deve precedere la divisione. Il for esteso visita ogni valore senza usare un indice esplicito. Nella dama, una riga ha quattro caselle per cui (riga + colonna) % 2 == 1.

Soluzione commentata. I tre voti iniziali producono Media: 27.0; i quattro voti producono Media: 25.75. Con int[] voti = {}; il programma stampa Nessun voto disponibile e termina il metodo prima della divisione. La dama riempie le righe 0-2 e 5-7; le righe 3 e 4 restano vuote. Lo switch può usare case 6, 7 -> "fine settimana"; case 1, 2, 3, 4, 5 -> "feriale"; default -> "valore non valido";, come spiegato nel testo.

C05-G02 – Gli undici casi e i confini

Consegna. Esegui InteroComeStringaComparato.java con 0, 3, 10, -1 e 11, annotando prima la previsione delle due righe per ogni input. Prova poi ad avviarlo senza argomenti. Aggiungi il caso 11 a entrambe le forme e spiega perché il ramo finale resta necessario.

Criterio e feedback. Le versioni if e switch concordano per tutti e cinque i valori; senza argomenti compare l'istruzione d'uso. Dopo l'aggiunta, 11 ha una parola esplicita, ma -1 e 12 restano fuori intervallo. Se la prova del numero negativo stampa ancora «maggiore di dieci», il messaggio non descrive il dominio corretto.

Soluzione commentata. I primi tre valori producono zero, tre e dieci in entrambe le versioni; -1 e 11 producono fuori intervallo. La guardia su args.length evita l'accesso a args[0] quando manca l'argomento. Aggiungendo 11, il caso diventa parte del dominio, ma un programma che riceve un int può ancora ricevere molti altri valori: il ramo finale mantiene definito il comportamento ai confini.

C05-P01 – Pattern, record e casi dominati (avanzato)

Consegna. Parti da EsitiConPattern.java. Prima di eseguirlo, scrivi l'output previsto per un voto 27, un voto 15, un esito sospeso e null. Poi inverti i due casi Valido e osserva il messaggio del compilatore. Spiega perché uno dei casi non può più essere raggiunto. Ripristina l'ordine corretto e verifica la compilazione senza preview.

Criterio. I quattro rami sono esercitati; il caso con guardia precede quello generale; il switch è esaustivo sulla gerarchia sealed e tratta null esplicitamente. L'errore introdotto è descritto come dominanza, non come difetto della JVM.

Suggerimenti. Leggi Valido(int voto) come estrazione del campo dal record. Chiediti se un Valido con voto 27 corrisponde anche al pattern senza guardia. Un caso che cattura tutti i valori del successivo deve comparire dopo.

Soluzione commentata. L'output è promosso: 27, da recuperare: 15, sospeso: documenti mancanti, assente. Se case Valido(int voto) viene prima, comprende anche i valori per cui voto >= 18 ed elimina il ruolo del caso più specifico. Il compilatore rileva questa dominanza. Il materiale resta etichettato avanzato perché record e gerarchie sealed vengono sviluppati nei capitoli successivi; qui interessa il controllo di flusso.

C05-D01 – Un continue che non fa avanzare il ciclo

Consegna. Senza eseguire il frammento int i = 0; while (i < 3) { if (i == 1) continue; i++; }, segui tre passaggi del ciclo. Spiega perché non termina e riscrivilo in modo che i raggiunga 3 anche quando salta il resto del corpo.

Criterio. La risposta identifica i == 1 come punto fisso: continue ricomincia il while prima di i++. Una correzione sposta l'incremento prima del ramo oppure usa un for il cui aggiornamento avviene dopo continue.

Suggerimenti. Annota i all'inizio di ogni iterazione. Chiediti quale istruzione viene saltata quando il ramo è vero.

Soluzione commentata. Dopo la prima iterazione i vale 1. Alla successiva il continue salta i++, quindi la condizione resta vera e il valore non cambia. Scrivere i++; if (i == 1) continue; nel corpo elimina quel punto fisso; in un programma reale occorre anche verificare che l'ordine delle altre operazioni resti quello voluto.

C06-A01 – Tre nomi, tre funzioni

Consegna. Spiega che cosa cambia quando sposti Catalogo in un altro package, quando aggiungi un import per ArrayList e quando un modulo dichiara exports per il package che contiene una classe pubblica. Indica quale di queste operazioni abbrevia un nome nel sorgente.

Criterio e feedback. Le risposte devono distinguere identità della classe, scrittura del riferimento e accessibilità fra moduli. Se rispondi che import sposta ArrayList, confronta il suo nome qualificato prima e dopo l'import: resta java.util.ArrayList.

Soluzione commentata. Cambiare il package di Catalogo cambia il suo nome qualificato e richiede di aggiornare dichiarazione, cartelle e comandi di avvio. L'import di ArrayList consente di scrivere solo ArrayList nel file che lo dichiara; la classe rimane in java.util. exports permette ai moduli che leggono il modulo del catalogo di accedere ai tipi pubblici del package esportato.

C06-G01 – Due moduli e un servizio

Consegna. Compila i sorgenti in esempi/C06/servizio dalla radice di javamattone/revisione-java-25 con javac --release 25 --module-source-path esempi/C06/servizio/src -d build/mods -m javamattone.catalogo,javamattone.app. Avvia con java --module-path build/mods -m javamattone.app/javamattone.app.Main. Leggi i due module-info.java e indica chi usa il servizio, chi lo fornisce e quale package è esportato. Come diagnosi, rimuovi temporaneamente exports, poi requires, e confronta i due errori di compilazione. Ripristina i file alla fine.

Criterio. L'output positivo è Ciao dal modulo catalogo. L'applicazione dichiara requires e uses; il catalogo dichiara exports e provides. Il package dell'implementazione rimane interno. I due errori introdotti devono essere spiegati come mancanza di accessibilità e di leggibilità, rispettivamente.

Suggerimenti. import abbrevia un nome nel sorgente: non sostituisce requires e exports. ServiceLoader cerca un'implementazione dichiarata al sistema dei moduli. Se l'avvio fallisce, verifica che entrambe le cartelle dei moduli siano sotto il percorso passato a --module-path.

Soluzione commentata. Il modulo javamattone.app legge javamattone.catalogo e usa l'interfaccia Saluto. Il modulo del catalogo esporta javamattone.catalogo.api e fornisce SalutoItaliano, che appartiene a un package non esportato. ServiceLoader ottiene il servizio senza che l'applicazione nomini la classe concreta. Rimuovere exports impedisce all'altro modulo di usare l'interfaccia pubblica; rimuovere requires impedisce al modulo chiamante di leggere il modulo che la contiene. Non serve aprire il package interno con opens per questo esempio: provides dichiara la relazione necessaria.

Consegna. Progetta due moduli: uno offre un catalogo di libri, l'altro lo usa. Il catalogo ha un package api con un'interfaccia pubblica e un package interno con l'implementazione. Scrivi a parole quali package esportare e quale modulo deve dichiarare requires. Motiva la scelta dal punto di vista di chi usa il catalogo.

Criterio. Il modulo del catalogo esporta soltanto il package dell'API; il modulo chiamante dichiara di leggere il catalogo. L'implementazione interna non diventa pubblica per caso. La risposta distingue visibilità del tipo Java e accessibilità attraverso il confine del modulo.

Suggerimenti. Parti dal codice che il chiamante deve scrivere e identifica il tipo che nomina. Un package interno può restare accessibile ai componenti del proprio modulo senza essere esportato.

Soluzione commentata. Nel module-info.java del catalogo si dichiara exports catalogo.api;; nel modulo dell'applicazione requires catalogo;. La classe dell'implementazione in catalogo.interno resta nascosta agli altri moduli. Se il programma vuole caricarla come servizio, si aggiungono uses e provides con un contratto esplicito, come nell'esempio guidato.

C06-D01 – Il percorso sbagliato all'avvio

Consegna. Hai compilato i moduli del programma guidato in build/mods. Un collega tenta java -cp build/mods javamattone.app.Main e riceve un errore di classe non trovata. Spiega perché il comando non rispecchia la struttura compilata e scrivi l'avvio corretto.

Criterio. La risposta usa --module-path build/mods e -m javamattone.app/javamattone.app.Main. Distingue il class path, che cerca classi, dal module path, che presenta moduli nominati al sistema.

Suggerimenti. Guarda le cartelle prodotte dall'opzione -d nella compilazione modulare e leggi il module-info.java del modulo da avviare.

Soluzione commentata. build/mods contiene directory di moduli compilati, non direttamente la classe nel percorso che il comando con -cp sta cercando. L'avvio è java --module-path build/mods -m javamattone.app/javamattone.app.Main: identifica il percorso dei moduli e il modulo con la sua classe principale.

Prova gli esempi

Per eseguire i programmi serve JDK 25. Puoi scaricare i singoli file Java collegati nel capitolo oppure il progetto completo, che contiene istruzioni e uno script di avvio. Le spiegazioni confrontano anche l’output atteso: prevedilo prima di eseguire il programma.

Massimiliano Tarquini · CC BY-NC 4.0

Torna all’inizio ↑