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.