Oggetti e classi
Un oggetto è semplicemente un contenitore di dati e funzioni per manipolarli. L’oggetto è quindi un componente autonomo di una applicazione che incapsula nella stessa struttura tutto quello che serve per gestire una porzione dell’informazione dell’applicazione e tutta la logica applicativa necessaria per scriverla, leggerla e modificarla.
Un esempio di oggetto è l’anagrafica di una persona, una struttura dati che contiene il nome, il cognome, l’indirizzo di residenza, la data di nascita. Un oggetto contiene sia questa struttura dati sia tutte le funzioni necessarie per utilizzarla, ad esempio per inserire i dati, per stamparli, ma anche per effettuare delle elaborazioni.
In Java, gli oggetti vengono definiti mediante il concetto di classe. Una classe è un “prototipo”, cioè un modello generale che definisce un tipo di oggetto, cioè i suoi dati, detti proprietà, e le sue funzioni, dette metodi. A partire dalla classe è poi possibile creare i veri propri oggetti, detti anche istanze della classe.
Vediamolo con un esempio:
class Persona {
String nome;
String cognome;
public Persona(String nome, String cognome) {
this.nome = nome;
this.cognome = cognome;
}
public void print() {
System.out.println("Nome: " + this.nome + " - Cognome: " + this.cognome);
}
}
Vediamo ora il significato di questo codice:
- Persona è una classe, e quindi un tipo di dato per cui è possibile creare variabili di quel tipo.
- all’interno della classe per prima cosa si definiscono le proprietà, in questo caso useremo il tipo String.
- la classe definisce un metodo speciale, detto costruttore, che ha lo stesso nome della classe e viene sempre eseguito quando si crea una nuova istanza della classe. Il costruttore può avere dei parametri ma non ha un tipo di ritorno, nemmeno void.
- la parola chiave public indica che il metodo è pubblico. Questo vuol dire che questa funzione dell’oggetto è richiamabile dall’esterno. Il costruttore ha una regola particolare: è di norma pubblico (vedremo poi le eccezioni) ma non viene richiamato esplicitamente. Il metodo print è pubblico e può essere richiamato esplicitamente.
- La parola chiave this viene utilizzata per accedere a proprietà e metodi interni dell’oggetto. E’ importante capire che this si riferisce sempre all’istanza dell’oggetto e non alla classe.
Ora creiamo l’applicazione che utilizza la classe (si ricorda di creare un file chiamato ApplicazionePersona.java).
Persona persona = new Persona("Mario", "Rossi");
persona.print();
Spiegazione:
- l’oggetto persona è di tipo Persona. La classe definisce un tipo di dato nuovo. la parola chiave new indica che bisogna creare una nuova istanza della classe Persona.notare che la creazione richiede dei parametri che sono gli argomenti del costruttore.
- La creazione di un nuovo oggetto prevede quindi questi passaggi:
- viene creato un oggetto vuoto di tipo Persona;
- viene eseguito il costruttore sull’oggetto, che valorizza le proprietà dell’oggetto;
- viene restituito l’oggetto costruito, che in questo caso viene salvato in “persona”.
- Il metodo print stampa quindi a schermo il nome e cognome della persona.
Siccome le classi sono prototipi, possono creare più oggetti dello stesso tipo:
Persona persona1 = new Persona("Mario", "Rossi");
Persona persona2 = new Persona("Lucia", "Bianchi");
persona1.print();
persona2.print();
Un oggetto quindi contiene proprietà e metodi, detti membri della classe.
Proprietà
Le proprietà sono le variabili locali della classe e quindi degli oggetti che crea. Sono di norma definite all’inizio della dichiarazione della classe. Per accedere ad una proprietà da un qualsiasi metodo della classe si usa la parola chiave this.
Metodi
I metodi sono le funzioni locali della classe: hanno quindi un tipo di ritorno (void se non c’è tipo di ritorno), un nome e opzionalmente dei parametri. La definizione di una funzione viene chiamata firma.
E’ possibile creare due metodi con lo stesso nome ma con firma differente, ovvero con parametri differenti. Vediamo un esempio:
public class Sommatore {
public int add(int a, int b) {
return a+b;
}
public int add(int a, int b, int c) {
return a+b+c;
}
Questa tecnica si chiama overload, e consente di richiamare la stessa funzione con parametri diversi. Java capisce a quale funzione si riferisce in base alla firma ed esegue la funzione corretta.
Incapsulamento
La parola chiave public indica un “modificatore di accesso”, cioè una regola che dice a Java se quella proprietà o quel metodo possono essere visibili (e quindi utilizzabili) da fuori dall’oggetto. Una proprietà public è accessibile dall’esterno, al contrario una proprietà private non è accessibile dall’esterno. Le proprietà e i metodi private sono sempre accessibili dall’interno dell’oggetto.
Questo meccanismo di protezione viene chiamato incapsulamento, e viene utilizzato non solo per ragioni di sicurezza, ma per organizzare il codice in modo tale che nemmeno per errore si possa accedere a risorse in modo incontrollato.
Vediamo un esempio con una nuova classe (si ricorda come sempre di crearla in un nuovo file con lo stesso nome della classe ApplicazioneContoCorrente ed estensione .java).
class ContoCorrente {
private Persona proprietario;
private float saldo;
public ContoCorrente(Persona persona) {
this.proprietario = persona;
this.saldo = 0;
}
public void print() {
System.out.println("Anagrafica:");
this.proprietario.print();
System.out.println("Saldo:" + this.saldo);
}
public void versa(float quantita) {
this.saldo += quantita;
}
public void preleva(float quantita) {
this.saldo -= quantita;
}
}
public class ApplicazioneContoCorrente {
public static void main(String[] args) {
Persona persona = new Persona("Mario", "Rossi");
ContoCorrente conto = new ContoCorrente(persona);
conto.versa(100);
conto.preleva(50);
conto.print();
}
}
la classe Persona è omessa ma identica a quanto visto sopra.
L’incapsulamento impedisce di modificare il saldo dall’esterno, è sempre necessario passare dai metodi versa o preleva. Non è possibile nemmeno modificare il proprietario, una volta creato l’oggetto.
Come si può vedere la programmazione ad oggetti semplifica il modo in cui progettiamo l’applicazione perché ci consente di suddividere il problema per oggetti e classi ognuno responsabile di una parte specifica della nostra applicazione.
Getter e Setter
Come si è visto, si può mettere una proprietà come privata. Si può decidere tuttavia di scrivere dei metodi pubblici per leggere e/o scrivere sulla proprietà, senza dare però l’accesso diretto.
class Persona {
private String nome;
private String cognome;
public Persona(String nome, String cognome) {
this.nome = nome;
this.cognome = cognome;
}
getNome() {
return this.nome;
}
getCognome() {
return this.nome;
}
public void print() {
System.out.println("Nome: " + this.nome + " - Cognome: " + this.cognome);
}
}
Sebbene non obbligatorio, è buona prassi tenere sempre le proprietà private, e creare appositi getter e setter pubblici quando servono.
Tipi valore e tipi riferimento
In Java i tipi base sono detti tipi valore. La variabile contiene effettivamente il dato. Gli oggetti invece sono detti tipi riferimento, un concetto simile a quello delle variabili puntatore. In altre parole l’identificatore della variabile di un oggetto non contiene l’oggetto, ma un riferimento all’oggetto, che resta in memoria in una zona non accessibile direttamente dal programmatore, ovvero nello Heap. I tipi valore invece sono sempre variabili locali, e quindi sono nello Stack.
Vediamo cosa cambia.
Shallow copy e Deep copy
Non è possibile duplicare un oggetto con l’operatore di assegnazione.
Se ad esempio scriviamo:
Persona persona1 = new Persona("Mario", "Rossi");
Persona persona2 = persona1;
Non creiamo un nuovo oggetto persona, duplicato ma indipendente dal primo, ma creiamo un alias, cioè nuovo riferimento allo stesso oggetto. Quindi se cambiamo il nome di persona1, siccome persona2 è un alias di persona1, allora anche persona2 vedrà cambiato il nome. Questo tipo di copia viene chiamato “shallow copy” ovvero “copia ombra”.
Per eseguire una “deep copy” cioè una “copia profonda”, dobbiamo creare una nuova istanza e poi duplicare tutte le proprietà del primo oggetto.
Persona persona1 = new Persona("Mario", "Rossi");
Persona persona2 = new Persona(persona1.nome, persona1.cognome);
Naturalmente questo è possibile solo se le proprietà del primo oggetto sono pubbliche (in questo esempio abbiamo ipotizzato che lo siano).
Passaggio per riferimento
Se un oggetto viene passato come parametro ad una funzione, non viene eseguita una deep copy, ma una shallow copy. Quindi se l’oggetto viene modificato dentro la funzione, viene modificato anche l’originale. Coi tipi base invece il passaggio è sempre per valore, cioè viene creata una copia, ed anche se modificata, non modifica l’originale.
Garbage collector
Java non prevede un meccanismo per “liberare la memoria” una volta che smettiamo di usare un determinato tipo riferimento. Questo perché lo fa automaticamente mediante un meccanismo chiamato garbage collector che al termine di ogni funzione controlla se esistono tra le variabili in uso dei tipi riferimento e se non ve ne sono, cancella gli oggetti in memoria non più referenziati. Per i tipi valore invece siccome sono nello stack, vengono eliminati all’uscita della funzione.
Proprietà e metodi statici
Una classe può avere proprietà o metodi definiti statici, ovvero che fanno parte della classe, ma non degli oggetti che si possono creare dalla classe. Vediamo un esempio:
public class Cerchio {
private double raggio;
// Costante statica
public static final double PI_GRECO = 3.141592653589793;
public Cerchio(double raggio) {
this.raggio = raggio;
}
public double area() {
return PI_GRECO * raggio * raggio;
}
// Metodo statico, calcola area senza bisogno di creare un cerchio
public static double calcolaArea(double r) {
return PI_GRECO * r * r;
}
}
In questo tipo di classe abbiamo una proprietà statica che memorizza il valore di Pi greco, ed un metodo statico che calcola l’area di un cerchio dato il raggio.
Metodi e proprietà statiche (marcati come public) possono essere usati SENZA creare una istanza della classe, e quindi si comportano esattamente come le funzioni della programmazione strutturata. Se sono invece marcate come private possono essere usati sempre senza creare una istanza, ma solo dai membri interni della classe.
I metodi statici esistono per fornire una funzione di utilità generale senza creare per forza una classe, ad esempio nelle operazioni di tipo matematico. Di contro possono occupare memoria inutilmente: a differenza gli oggetti, che vengono creati e distrutti dinamicamente in base alle esigenze del programma, i membri statici di una classe restano in memoria per l’intera durata dell’applicazione.
Un metodo statico particolare è il metodo main() che abbiamo già visto nella prima lezione. Esso definisce il punto di accesso dell’applicazione quando questa viene eseguita. Deve quindi esistere almeno una classe dove sia presente per permettere l’esecuzione del programma.
Conclusioni
.Riassumiamo i concetti della lezione:
- Java è un linguaggio ad oggetti. Gli oggetti sono contenitori di dati e funzioni a loro collegate.
- Per creare gli oggetti sono usano le classi, ovvero dei prototipi che definiscono le caratteristiche degli oggetti che possono creare.
- Le classi prevedono proprietà e funzioni, ed una funzione speciale detta costruttore.
- Gli oggetti si creano con la parola chiave new e passando al costruttore eventuali parametri.
- Gli oggetti prevedono l’incapsulamento, un sistema per decidere quali proprietà e metodi non sono visibili all’esterno.
- Gli oggetti sono tipi riferimento, quindi l’oggetto viene memorizzato nello Heap e la variabile oggetti ne contiene solo il riferimento.
- Il garbage collector si occupa della cancellazione dalla memoria di oggetti non più usati.
Nella prossima lezione vedremo le classi e gli oggetti più importanti della libreria Java.