Utilizzo degli oggetti in Java

Questa lezione introduce in modo pratico la gestione degli oggetti in Java, evidenziando come crearli, inizializzarli e sfruttarli al meglio nei programmi.

Vengono presentati costruttori, metodi di accesso e metodi di modifica, mostrando come ogni aspetto concorra a definire un uso ottimale della programmazione orientata agli oggetti.

Variabili Oggetto e Oggetti

Per lavorare con gli oggetti, bisogna prima costruirli e specificarne lo stato iniziale. Successivamente si applicano i metodi ad essi.

Nel linguaggio di programmazione Java, si usano i costruttori per costruire, appunto, nuove istanze di una classe. Un costruttore è un metodo speciale il cui scopo è costruire e inizializzare oggetti.

Vediamo un esempio. La libreria standard di Java contiene una classe Date. I suoi oggetti descrivono punti nel tempo, o meglio istanti, come il 14 ottobre 2014, 22:32:45 GMT.

I costruttori hanno sempre lo stesso nome della classe a cui appartengono. Per cui, il costruttore della classe Date si chiama Date. Per costruire un oggetto di tipo Date, bisogna adoperare l'operatore new seguito dal nome del costruttore:

new Date();

Questa espressione costruisce un nuovo oggetto. L'oggetto viene, inoltre, inizializzato con la data e l'ora corrente in questo caso.

Definizione

Creazione di un oggetto in Java

In Java, per costruire un oggetto di una classe si adopera l'operatore new seguito dall'invocazione del costruttore della classe stessa.

Il costruttore è un metodo della classe che ha lo stesso nome ed ha lo scopo di inizializzare l'oggetto appena costruito.

La sintassi per costruire un oggetto è la seguente:

new NomeClasse();

Dato che il risultato di questa espressione è un nuovo oggetto, quest'ultimo può essere passato come argomento ad un metodo. Ad esempio, volendo stampare la data attuale, si può scrivere:

System.out.println(new Date());

Oppure si può invocare un metodo in maniera diretta. Ad esempio, un oggetto di tipo Date fornisce il metodo toString che restituisce una rappresentazione testuale, cioè una stringa, della data e dell'ora corrente. Per cui, si può scrivere:

String adesso = new Date().toString();

In entrambe gli esempi visti sopra, l'oggetto appena costruito viene usato una volta sola. Nei programmi Java, tipicamente si costruiscono oggetti e li si mantiene in memoria per poterli adoperare più volte in punti diversi.

Per far questo bisogna memorizzare l'oggetto appena creato in una variabile oggetto. La sintassi è la seguente:

Date adesso = new Date();

La variabile adesso punta all'oggetto appena costruito di tipo Date come mostra la figura che segue:

Variabile che punta all'oggetto appena creato
Figura 1: Variabile che punta all'oggetto appena creato

Esiste un'importante differenza tra oggetti e variabili oggetto. Per comprenderla, prendiamo l'esempio che segue:

// La variabile adesso non si riferisce ad alcun oggetto
Date adesso;

Con questa riga di codice abbiamo definito una variabile oggetto, adesso, che può far riferimento ad un oggetto di tipo Date. Ma è importante capire che la variabile adesso non è un oggetto. Infatti, al momento non si riferisce ancora a nessun oggetto.

Se proviamo ad eseguire il codice che segue otteniamo un errore di compilazione:

String s = adesso.toString();
Definizione

Variabile Oggetto

In Java, una Variabile Oggetto è una variabile che contiene un riferimento ad un oggetto in memoria. Una variabile oggetto non contiene l'oggetto stesso.

Una variabile oggetto deve essere prima inizializzata.

Esistono due modi per farlo:

  1. Si può inizializzare una variabile oggetto direttamente con un oggetto costruito al momento della dichiarazione:

    Date adesso = new Date();
    
  2. Si può inizializzare una variabile oggetto in un secondo momento, assegnandole un oggetto costruito in precedenza:

    Date data_1 = new Date();
    Date data_2 = data_1;
    

    Facendo così, la variabile data_2 punta allo stesso oggetto a cui punta data_1, come mostra la figura che segue:

    Due variabili oggetto che puntano allo stesso oggetto
    Figura 2: Due variabili oggetto che puntano allo stesso oggetto
Definizione

Invocazione di un metodo su un oggetto

In Java, un metodo è una funzione che opera su un oggetto. Per invocare un metodo su un oggetto si adopera la notazione punto . su di una variabile oggetto che contiene il riferimento ad esso.

La sintassi è la seguente:

NomeClasse variabileOggetto = new NomeClasse();

variabileOggetto.nomeMetodo();

Il concetto importante da comprendere è che una variabile oggetto non contiene l'oggetto stesso. In realtà, contiene un riferimento all'oggetto. Questo riferimento è un'informazione che dice al programma dove trovare l'oggetto in memoria.

In particolare, in Java il valore di qualunque variabile oggetto è un riferimento ad un oggetto memorizzato in memoria da qualche parte. Il risultato dell'operatore new è a sua volta un riferimento.

Per cui, un'istruzione del genere è composta di due parti:

Date adesso = new Date();
  1. L'espressione new Date() crea un oggetto di tipo Date e restituisce un riferimento a quest'ultimo;
  2. L'operatore di assegnamento = assegna il riferimento restituito dalla chiamata al costruttore alla variabile adesso.
Consiglio

Variabili Oggetto e Puntatori

Il concetto di Variabile Oggetto può risultare più chiaro a coloro che conoscono il linguaggio C e C++.

In questi linguaggi, si usano i puntatori per riferirsi ad oggetti in memoria. Un puntatore è una variabile che contiene l'indirizzo di memoria di un oggetto.

In Java i puntatori non esistono, ma il concetto di Variabile Oggetto è simile. Una variabile oggetto contiene un riferimento ad un oggetto in memoria, ma non l'oggetto stesso.

In Java, inoltre, è possibile fare in modo che una variabile oggetto non punti ad alcun oggetto. Per fare ciò è sufficiente assegnare alla variabile il valore null. Ad esempio:

Date adesso = null;

Studieremo in dettaglio il valore null più avanti, nelle prossime lezioni.

Una buona pratica di programmazione è quella di controllare che una variabile oggetto non si riferisca a null prima di usarla. Ad esempio potremmo scrivere:

if (adesso != null) {
    System.out.println(adesso);
}
Definizione

Variabili Oggetto e Inizializzazione

Normalmente una variabile oggetto non inizializzata contiene un riferimento a null. Questo significa che la variabile non punta ad alcun oggetto in memoria.

Per inizializzare una variabile oggetto, bisogna assegnarle un riferimento ad un oggetto costruito. Questo riferimento può essere ottenuto direttamente con l'operatore new o assegnandole un riferimento già esistente:

NomeClasse variabileOggetto = new NomeClasse();
NomeClasse variabileOggetto = riferimento;

Esempio di Progettazione ad Oggetti

Negli esempi di sopra abbiamo adoperato la classe Date della libreria standard di Java. Un'istanza della classe Date ha uno stato, nel dettaglio un particolare istante nel tempo.

La classe Date nasconde i dettagli di come l'istante sia memorizzato. A onor di cronaca, la classe Date memorizza l'istante come il numero di millisecondi trascorsi dal 1 gennaio 1970, 00:00:00 UTC. Questo è noto come tempo Unix o epoch.

UTC sta per Coordinated Universal Time, lo standard scientifico per la misura del tempo che, per tutti gli scopi pratici, è identico al GMT o Greenwich Mean Time, il fuso orario di Greenwich, Londra.

Per come è fatta, la classe Date non è molto utile per manipolare il tipo di informazione sulle date che le persone sono abituate ad usare. Per esempio, in Italia, usiamo dare una data nel formato Giorno-Mese-Anno. Negli stati anglosassoni, invece, si usa il formato Mese-Giorno-Anno. Esistono poi altri modi di misurare il tempo come il calendario lunare cinese o il calendario ebraico.

Coloro che hanno progettato la libreria standard di Java hanno deciso di sfruttare il principio della separation of concerns (dall'inglese separazione degli interessi). Questo principio asserisce che un sistema complesso dovrebbe essere diviso in parti più piccole, ognuna delle quali si occupa di un singolo aspetto del sistema.

Per cui, i progettisti hanno deciso di separare la funzione di tenere traccia di un istante nel tempo, classe Date, dalla funzione di assegnare un nome a quell'istante in base ad un calendario. Per tal motivo hanno creato la classe LocalDate.

Separare la misura del tempo dal calendario è un ottimo esempio di progettazione ad oggetti. In generale, è sempre una buona idea usare classi diverse per esprimere concetti diversi.

Per costruire un oggetto di tipo LocalDate, non si adopera un costruttore. Si utilizza bensì un factory method, che potremmo tradurre con metodo di costruzione. Studieremo i metodi factory più avanti. Per ora basta sapere che essi forniscono un modo alternativo per costruire oggetti.

Ad esempio, l'espressione:

LocalDate oggi = LocalDate.now();

costruisce un nuovo oggetto oggi di tipo LocalDate adoperando il metodo factory now. Questo oggetto rappresenta la data attuale.

Possiamo costruire anche un oggetto con una data fornita da noi. Ad esempio, per costruire un oggetto natale di tipo LocalDate che rappresenta il 15 agosto 2021, si può scrivere:

LocalDate ferragosto = LocalDate.of(2021, 8, 15);

Una volta che abbiamo un oggetto LocalDate, possiamo recuperare l'anno, il mese o il giorno sfruttando i metodi che la classe mette a disposizione. Ad esempio:

int anno = ferragosto.getYear();
int mese = ferragosto.getMonthValue();
int giorno = ferragosto.getDayOfMonth();

System.out.println(giorno + "/" + mese + "/" + anno);

L'esempio potrebbe sembrare strano dal momento che stiamo riprendendo i valori che abbiamo adoperato per costruire l'oggetto. Tuttavia, potrebbe capitare che i valori siano il risultato di un calcolo, per cui potrebbe essere utile ottenere i valori in questo modo.

Ad esempio, uno dei metodi che LocalDate mette a disposizione è plusDays che permette di aggiungere un numero di giorni ad una data. Ad esempio, per ottenere la data di oggi più 1000 giorni si può scrivere:

LocalDate oggi = LocalDate.now();
LocalDate traMilleGiorni = oggi.plusDays(1000);

String data = traMilleGiorni.getDayOfMonth() + "/"
            + traMilleGiorni.getMonthValue() + "/"
            + traMilleGiorni.getYear();

System.out.println(data);

La classe LocalDate ha incapsulati al proprio interno i campi necessari per memorizzare la data su cui un oggetto di tipo LocalDate si riferisce. Senza guardare il codice sorgente, non possiamo sapere come la classe LocalDate memorizzi la data. Questo è un esempio di incapsulamento. Ovviamente, il punto cruciale è che ciò non importa. Importano solo i metodi che la classe espone.

Attraverso la classe LocalDate abbiamo visto all'opera due concetti fondamentali della programmazione ad oggetti: incapsulamento e separation of concerns.

Definizione

Separation of Concerns

La Separation of Concerns è un principio di progettazione software che afferma che un sistema complesso dovrebbe essere diviso in parti più piccole, ognuna delle quali si occupa di un singolo aspetto del sistema.

Metodi di accesso e Metodi di modifica

Riprendiamo un attimo l'esempio di calcolo della data tra 1000 giorni da adesso.

Per effettuare il calcolo abbiamo adoperato la seguente espressione:

LocalDate traMilleGiorni = oggi.plusDays(1000);

Quello che ci domandiamo ora è: che cosa è accaduto all'oggetto oggi dopo aver invocato il metodo plusDays?. La risposta è: nulla.

Il metodo plusDays non modifica l'oggetto oggi. Invece, restituisce un nuovo oggetto LocalDate che rappresenta la data oggi più 1000 giorni. L'oggetto originale rimane immutato.

Il metodo plusDate è un tipo di metodo che non modifica l'oggetto su cui è invocato ma serve per accedere al suo contenuto o stato. Tali metodi sono chiamati metodi di accesso o Accessor Methods.

Definizione

Metodi di Accesso

Un Metodo di Accesso o Accessor Method è un metodo che permette di accedere allo stato di un oggetto senza modificarlo.

Un oggetto di tipo LocalDate è un oggetto immutabile. Questo significa che una volta costruito, non può essere modificato. Se si vuole cambiare la data, bisogna costruire un nuovo oggetto.

L'immutabilità di un oggetto si ottiene semplicemente, nella programmazione ad oggetti, non fornendo metodi che permettano di modificare lo stato dell'oggetto. In questo modo, l'oggetto è protetto da modifiche accidentali.

Un altro esempio di oggetto immutabile è la classe String. Una volta costruita, una stringa non può essere modificata. Se ne può costruire una nuova a partire da quella originale, ma la stringa originale rimane invariata.

Volendo concatenare due stringhe, si può scrivere:

String saluto = "Ciao";
String nome = "Mondo";

String messaggio = saluto + ", " + nome + "!";

In questo caso, la stringa messaggio contiene il testo "Ciao, Mondo!". La stringa saluto e la stringa nome rimangono invariate.

Definizione

Oggetto Immutabile

Un Oggetto Immutabile è un oggetto che, una volta costruito, non può essere più modificato.

Esiste una versione mutabile, ossia modificabile, della classe String chiamata StringBuilder. Questa classe permette di modificare il contenuto della stringa. Ad esempio, per costruire la stringa messaggio si può scrivere:

StringBuilder messaggio = new StringBuilder();
messaggio.append("Ciao");
messaggio.append(", ");
messaggio.append("Mondo");
messaggio.append("!");

System.out.println(messaggio.toString());

In questo caso il metodo append modifica la stringa messaggio aggiungendo il testo passato come argomento. Questo tipo di metodo è chiamato metodo di modifica o Mutator Method. Attraverso di essi si può modificare lo stato dell'oggetto.

Definizione

Metodi di Modifica

Un Metodo di Modifica o Mutator Method è un metodo che permette di modificare lo stato di un oggetto.

In Sintesi

In questa lezione anche se non abbiamo visto ancora come si può realizzare una classe definita dall'utente, abbiamo studiato il modo in cui un oggetto può essere usato.

Ci siamo messi, cioè, dal punto di vista di chi usa un oggetto, non da chi lo crea.

In particolare, abbiamo studiato che:

  • Per costruire un oggetto si adopera un costruttore e l'operatore new;
  • Una variabile oggetto contiene un riferimento ad un oggetto, non l'oggetto stesso;
  • Attraverso una variabile oggetto si può agire sull'oggetto stesso;
  • Esistono anche metodi factory, ossia metodi che costruiscono oggetti;
  • Un oggetto può essere immutabile o mutabile a seconda dei metodi che espone.

A partire dalla prossima lezione, vedremo come si può creare una classe definita dall'utente.