Lezione 5 · Canale 2 · lunedì 5 ottobre 2026
Oggetti, librerie e controllo in Java
Progettazione del Software
Riassunto
Le variabili oggetto contengono riferimenti a oggetti nell'heap: assegnare una variabile a un'altra può farle riferire allo stesso oggetto e causare side effect. null è distinto da un oggetto vuoto. La parola chiave new alloca la memoria dell'oggetto; il garbage collector recupera gli oggetti non raggiungibili senza richiedere una deallocazione esplicita. Le librerie organizzano le classi in pacchetti; l'istruzione import permette di utilizzarle. Per leggere da tastiera si usano stream e classi come BufferedReader o Scanner. Lo scope limita la visibilità delle variabili ai blocchi in cui sono dichiarate. I tipi primitivi rappresentano valori e hanno dimensioni fissate; le classi wrapper permettono di convertire tra primitivi e oggetti. Le istruzioni if e switch permettono scelte condizionali; i cicli while e for permettono ripetizioni. Per le stringhe, l'operatore == confronta i riferimenti (uguaglianza superficiale) mentre equals confronta il contenuto (uguaglianza profonda). String è immutabile; StringBuilder è mutabile e permette modifiche condivise su uno stesso oggetto.
Concetti chiave
- Riferimento a oggetto — valore contenuto in una variabile oggetto che identifica un oggetto allocato nell'heap.
- Side effect — modifica osservabile attraverso un'altra variabile che riferisce lo stesso oggetto.
null— valore speciale che indica che una variabile oggetto non riferisce alcun oggetto.- Garbage collector — servizio della Java Virtual Machine che recupera la memoria degli oggetti non più raggiungibili dal programma, senza richiedere una deallocazione esplicita.
- Libreria — collezione di classi riutilizzabili nei programmi.
- Pacchetto — unità di distribuzione che organizza classi e permette di accedere a una libreria.
- Stream — flusso di dati elaborato progressivamente, per esempio carattere per carattere.
- Lettura bufferizzata — modalità che raccoglie i dati e li rende disponibili insieme, per esempio una riga alla volta.
- Scope (ambito di visibilità) — porzione di codice in cui una variabile è visibile; in Java è delimitata dai blocchi tra parentesi graffe.
- Tipo primitivo — tipo che rappresenta un valore, non un riferimento a un oggetto.
- Classe wrapper — classe, come
Integer, che fornisce metodi per convertire tra valori primitivi e oggetti o per operare su tali valori. - Uguaglianza superficiale — confronto con
==che, tra variabili oggetto, verifica se i riferimenti identificano lo stesso oggetto. - Uguaglianza profonda — confronto del contenuto mediante
equals, se la classe implementa il metodo con questa semantica. StringBuilder— classe mutabile usata per costruire e modificare sequenze di caratteri.
Sviluppo
Riferimenti, heap e stack
L'oggetto viene rappresentato come una porzione di memoria nell'heap, mentre nel record di attivazione corrente sullo stack la variabile contiene il suo indirizzo. Se a T si assegna S (T = S), entrambe contengono lo stesso riferimento e puntano allo stesso oggetto. La rappresentazione aiuta a capire perché due variabili oggetto possono interferire: modificando l'oggetto tramite una, la modifica può essere osservata anche tramite l'altra. Con i tipi primitivi, invece, le variabili hanno caselle distinte e modificare il valore di una non cambia quello dell'altra.
null e oggetto non creato
Una variabile oggetto può contenere null, che indica che non riferisce alcuna memoria allocata per un oggetto. null è un valore speciale e non va confuso con l'indirizzo 0000, che identifica una cella di memoria. Un riferimento null è diverso da un oggetto vuoto o da una stringa vuota.
Creare oggetti con new
La parola chiave new alloca nell'heap la memoria necessaria alla struttura dell'oggetto e fa sì che il riferimento sia assegnato alla variabile. La sintassi è new seguito dal nome della classe e dagli eventuali parametri usati per inizializzare l'oggetto. Concettualmente è analogo alla malloc del C; in Java la memoria viene gestita automaticamente dalla JVM.
Stringa vuota e null
Una stringa può essere creata passando caratteri come parametro, per esempio new String("ciao"). Creare una stringa senza caratteri produce una stringa vuota, distinta da null: nel primo caso esiste un oggetto che contiene zero caratteri; nel secondo la variabile non riferisce alcun oggetto.
Riassegnazione dei riferimenti e garbage collector
Se S1 e S2 sono riferimenti a due stringhe diverse e poi si assegna S2 a S1, entrambe le variabili riferiscono la seconda stringa. La prima rimane senza riferimenti, se nessun'altra variabile la indica, e può essere recuperata dal garbage collector. In Java non si usa una cancellazione esplicita degli oggetti: il garbage collector è un servizio della JVM.
Librerie e libreria standard
Una libreria Java è una collezione di classi che si possono riutilizzare, evitando di riscrivere funzionalità già disponibili. La libreria standard comprende classi fornite con l'ambiente Java, come String e System; altre librerie possono essere prodotte dalla comunità o offerte da piattaforme come Android. La documentazione accompagna normalmente le librerie e spiega come usarne le classi.
Pacchetti e istruzione import
I pacchetti organizzano le classi per la distribuzione e l'accesso. Tra gli esempi ci sono java.lang (che comprende String ed è importato automaticamente), java.awt e javax.swing per le interfacce grafiche, java.io per input e output, java.net per la rete, java.util per utilità e java.text per la formattazione del testo. L'istruzione import permette di usare nel programma classi appartenenti a un pacchetto; si può importare una classe specifica oppure, con *, tutte le classi di quel pacchetto. Negli esercizi è pratico importare il pacchetto intero; nelle distribuzioni professionali è preferibile limitarsi alle classi necessarie per contenere le dimensioni del software.
Input da tastiera: stream e BufferedReader
System.in rappresenta l'input da tastiera. Poiché i caratteri arrivano come flusso, un InputStreamReader legge lo stream di input; un BufferedReader permette di leggere i dati in modo bufferizzato. La catena è System.in → InputStreamReader → BufferedReader; il metodo readLine() restituisce una riga fino all'Enter. Queste classi appartengono al pacchetto java.io.
Allocazione implicita e riferimenti usati come parametri
Non ogni oggetto deve essere memorizzato in una variabile. Un InputStreamReader viene creato con new e passato direttamente al costruttore di BufferedReader; il programma conserva in una variabile il BufferedReader, che userà per leggere più righe. Si costruisce così una catena di oggetti creati e passati come parametri.
Scanner per leggere una riga
Un'altra soluzione per la tastiera usa Scanner costruito a partire da System.in; il metodo nextLine() legge una riga. Scanner è un'alternativa di livello più alto a BufferedReader e mostra che una libreria può offrire più strumenti per risolvere problemi simili. Scanner si costruisce da qualunque InputStream o Readable, compreso lo stream di una connessione di rete. La scelta tra Scanner e BufferedReader riguarda efficienza e controllo. Scanner appartiene a java.util.
Scope e risoluzione dei nomi
Nell'esempio Scanner in = new Scanner(System.in), la parola in compare sia come campo di System sia come nome della variabile locale. System.in identifica esplicitamente l'input fornito da System; in seguito, in.nextLine() si riferisce alla variabile locale di tipo Scanner. La variabile è visibile nel blocco del metodo main, delimitato dalle parentesi graffe. La ricerca del nome procede dallo scope più locale verso quelli più esterni; quando un nome è mascherato, si può indicare esplicitamente quello esterno.
Tipi primitivi
I tipi primitivi rappresentano valori, mentre le variabili oggetto contengono riferimenti a oggetti nell'heap. Sono elencati i tipi numerici int, long, short, byte, float e double, oltre a char e boolean. Le dimensioni sono fissate dalla specifica del linguaggio: byte 8 bit, short 16, int 32, long 64, float 32 e double 64 bit (IEEE 754). char è a 16 bit e rappresenta un'unità di codice UTF-16 (valori da 0 a 65535). I nomi dei tipi primitivi si scrivono con iniziale minuscola.
Stringhe e valori primitivi in memoria
String è una classe immutabile e viene rappresentata come un oggetto nell'heap a cui lo stack fa riferimento. I valori primitivi, invece, sono memorizzati direttamente nelle variabili; per questo si descrivono come caselle indipendenti, senza il rischio di side effect dovuto a riferimenti condivisi. Tra gli oggetti incontrati sono String, Random, Scanner e gli oggetti forniti da System.
Conversione con le classi wrapper
I dati letti da tastiera sono caratteri e possono essere raccolti in una stringa; se si vuole usarli come numero, occorre convertirli. La classe wrapper Integer offre il metodo parseInt, che riceve una stringa e restituisce il corrispondente valore intero. Lo stesso vale quando si leggono dati da fonti come JOptionPane, che restituisce il testo inserito come stringa.
Scelta condizionata: if e switch
if sceglie un blocco di istruzioni se la condizione è vera e un blocco alternativo se è falsa. switch permette di associare blocchi a più etichette, usando case; break interrompe l'esecuzione dei casi successivi. Nell'esempio dei giorni del mese, si raggruppano sotto lo stesso blocco i mesi con 30 giorni (4, 6, 9 e 11), quelli con 31 e il mese di febbraio.
Cicli while e for
while ripete un blocco finché la condizione è vera; il numero di ripetizioni può dipendere dalle modifiche eseguite durante il ciclo. for raccoglie inizializzazione, condizione e incremento nella stessa istruzione. Nell'esempio for (i = 0; i < 10; i++), il ciclo stampa dieci asterischi. while si usa per cicli dal numero di ripetizioni dinamico; for per iterazioni il cui numero è definito dalle espressioni scritte nel codice.
Confrontare oggetti con == ed equals
Due stringhe con lo stesso contenuto, una creata con una stringa letterale e l'altra con new String(...), si confrontano diversamente con == e equals. L'operatore == confronta i riferimenti e, in questo caso, restituisce falso perché le stringhe sono allocate come oggetti distinti. equals confronta il contenuto delle stringhe e restituisce vero. Il primo è un confronto superficiale, il secondo profondo. equals non è necessariamente implementato con semantica profonda per ogni classe; per le classi definite dal programmatore può essere necessario ridefinirlo.
Concatenazione di stringhe immutabili
Con T = S.concat("burger"), concat produce una nuova stringa: S conserva il proprio contenuto e T riferisce la stringa concatenata. Il parametro letterale viene usato senza assegnarlo a una variabile. Questo esempio mostra l'immutabilità di String e il ruolo del garbage collector per gli oggetti che non sono più riferiti.
Modifiche condivise con StringBuilder
Con StringBuilder, append("burger") modifica l'oggetto esistente. Se T riceve il risultato di S.append(...), S e T riferiscono lo stesso oggetto modificato; per questo entrambe le stampe mostrano il contenuto aggiornato (per esempio «hamburger»). Questo illustra un side effect: un cambiamento fatto attraverso una variabile è visibile anche tramite l'altra.
equals di StringBuilder
Due oggetti StringBuilder distinti possono contenere lo stesso testo ma risultare diversi sia con == sia con equals. StringBuilder è un "costruttore di stringhe" (un oggetto mutabile in cui si compone il testo), non una String; il suo equals non confronta il contenuto come accade per String. Il comportamento di equals va verificato per la classe specifica.