Ruolo delle classi in Java

Le classi in Java possono avere due ruoli:
  1. Classi come prototipi (template) di oggetti;
  2. Classi come contenitori di variabili e metodi statici.
Questi due ruoli possono essere presenti contemporaneamente in una classe.


Nel primo ruolo, una classe descrive le caratteristiche comuni di una collezione di oggetti (si pensi ad esempio alla classe BankAccount). La dichiarazione di classe introduce un nuovo tipo di dato (la classe stessa), i cui elementi saranno i suoi oggetti.
Nella classe vengono dichiarati:
  • le variabili di istanza, che costituiscono lo stato degli oggetti;
  • i metodi di istanza, che realizzano le funzionalità degli oggetti;
  • i costruttori, che determinano le modalità di creazione degli oggetti.
Quando un oggetto viene creato con new, gli viene associata una copia di ogni variabile di istanza.


Per accedere ad una variabile di istanza di un oggetto, si usa la sintassi
<oggetto>.<nomeVariabile>
e analogamente per invocare un metodo, si scrive
<oggetto>.<nomeMetodo>(<listaParametri>)
Ad esempio,
 
BankAccount conto1 = new BankAccount(1000);
conto1.withdraw(500);
// crea un oggetto
// chiamata metodo





Variabili e metodi statici

In una classe possiamo anche dichiarare uno stato e delle funzionalità che sono associati alla classe stessa, e non ai suoi oggetti. Si possono infatti dichiarare:
  • variabili statiche, che costituiscono lo stato della classe;
  • metodi statici, che realizzano le funzionalità della classe.
A volte, variabili e metodi statici vengono chiamati di classe.


Ad esempio, abbiamo visto in diverse classi la dichiarazione del metodo
public static void main(String [] args)
che viene invocato quando si esegue la classe con il comando java.


Per accedere ad una variabile statica di una classe, si usa la sintassi
<nomeClasse>.<nomeVariabile>
e analogamente per invocare un metodo statico, si scrive
<nomeClasse>.<nomeMetodo>(<listaParametri>)
Ad esempio, tutte le variabili e i metodi della classe Math [locale, Medialab, Sun] sono statici:
 
System.out.print("Dammi il raggio: ");
double raggio = console.readDouble();
double volume = 4.0/3.0* Math.PI * Math.pow(raggio,3);
System.out.println("Il volume della sfera è: " + volume);



Dichiarazione di una classe

Lo schema generale di dichiarazione di una classe in Java è il seguente:
 
<modif> class <nome-classe> {
<costruttori>
<variabili statiche>
<metodi statici>
<variabili d'istanza>
<metodi d'istanza>
}

Variabili e metodi (di istanza o statici) sono chiamati anche membri della classe. L'ordine delle dichiarazioni all'interno del corpo di una classe non è importante.




Esempio: la classe Student

La classe Student contiene variabili e metodi sia statici che di istanza.
 
public class Student
{
   // variabili d'istanza
   public String name;  // nome
   public int ID;       // matricola
   public double test1, test2, test3;
                        // voti per tre esami

  // variabile statica: memorizza il più piccolo
  // numero di matricola disponibile

   private static int nextUniqueID = 1;

  // costruttore: fornisce il nome per lo
  // studente, e gli assegna una matricola unica

   Student(String theName) 
      {    name = theName;
           ID = nextUniqueID;
           nextUniqueID++;
      }

   // metodo d'istanza: calcola la media
   public double getAverage() 
      { return (test1 + test2 + test3) / 3;
      }

  // metodo statico: restituisce il prossimo
  // numero di matricola disponibile

   public static int getUniqueID() 
      { return nextUniqueID;
      }




"Snapshot" di una computazione


Si osservi che:
  • Esiste una sola classe Student (con le sue variabili statiche), ma ci sono più oggetti di tipo Student (ognuno con le sue variabili di istanza). Le variabili statiche possono essere considerate come globali.
  • Un metodo statico (come getUniqueID) appartiene alla classe e quindi non è parte di nessun oggetto. Non ha accesso alle variabili d'istanza di nessun oggetto.
  • Come abbiamo già visto, un metodo statico può essere invocato anche se non esiste alcun oggetto della classe (si pensi a Math.sqrt()). 

  • Il nome per invocarlo ha la forma <nomeClasse>.<nomeMetodo>.



Manipolazione di oggetti: un esempio

La seguente sequenza di comandi crea la collezione di oggetti vista prima:
 
Student std = 
new Student("John Smith");
Dichiara la variabile std, e la inizializza con un riferimento ad un nuovo oggetto della classe Student, avente nome "John Smith"
Student std1 = 
new Student("Mary Jones");
Dichiara std1, e lo inizializza con un riferimento a un nuovo studente, "Mary Jones"
Student std2 = std1; Dichiara std2, e lo inizializza con un riferimento ALLO STESSO OGGETTO riferito da std1
Student std3 = null; Dichiara std3, e lo inizializza a null

Si noti ancora una volta che le operazioni su variabili di tipo oggetto sono operazioni su riferimenti, non sugli oggetti stessi. Quindi, ad esempio, l'effetto del comando

    Student std2 = std1;
non è la creazione di una copia dell'oggetto, ma la sua condivisione.
 



Meglio membri statici o di istanza?

Quando si scrivono i metodi e le variabili di una classe, come si decide se devono essere statici o di istanza?

In generale, devono essere statici se codificano le funzionalità o lo stato della classe, e quindi sono significativi anche se non esiste alcun oggetto della classe.
Invece devono essere di istanza se codificano le funzionalità o lo stato degli oggetti.

Vediamo alcuni esempi più specifici, utili nella pratica:
 
 

Il metodo main è sempre statico.  Si tratta chiaramente di una funzionalità della classe: viene invocato per iniziare l'esecuzione, prima della creazione di qualunque oggetto.
Una variabile deve essere di istanza se, concettualmente, il suo valore può essere diverso per oggetti diversi. 
Esempio: coordinate di un punto.
Se fossero dichiarate statiche, tutti i punti avrebbero le stesse coordinate.
Una variabile dovrebbe essere statica se il suo valore non cambia da un oggetto all'altro. 
Esempio: il "numero di istanze della classe", oppure una costante.
Il compilatore non protesta se vengono dichiarate come variabili di istanza, ma il programma risulta concettualmente meno chiaro, e a volte errato.
Se un metodo accede ad una variabile d'istanza, non può essere statico. 
Esempio: toString, equals,...
Il compilatore segnalerebbe un errore.
Se un metodo non accede ad alcuna variabile d'istanza, dovrebbe essere statico. In caso contrario il compilatore non segnala errori, ma il programma è concettualmente poco chiaro.

 



public, private o niente?

Abbiamo visto in molti casi che una dichiarazione di variabile o metodo (sia statico che  di istanza) può essere preceduta dai modificatori public, private, o da nessuno dei due. Cosa significano?
 
 
Modificatore
Accessibilità: lettura o scrittura per variabili (<obj>.<variabile>) o invocazione per metodi (<obj>.<metodo>(<parametri>))
public
Sempre, cioè ovunque la classe di <obj>sia accessibile
nessuno
Accessibile solo nel package che contiene la classe
private
Accessibile solo all'interno della classe di <obj>



Un esempio concreto.

Vediamo come si può controllare l'accesso alle variabili name ed ID di un oggetto della classe Student. Con l'uso del costruttore già visto, le variabili name e ID vengono inizializzate al momento della creazione. Ma poiché le variabili d'istanza sono dichiarate public, è possibile una sequenza di comandi come
 

std1 = new Student("Mary Jones");
std1.name = "Paolo Rossi";
std1.ID = std.ID

che sarebbe chiaramente da evitare... 




Nuova versione di Student

Per impedire queste modifiche, possiamo dichiarale private, modificando la classe come segue:
 
public class Student
{
   // variabili d'istanza
   private String name;  // nome
   private int ID;       // matricola
   public double test1, test2, test3;
                        // voti per tre esami

    private static int nextUniqueID = 1;

  // costruttori e metodi come prima;

  // nuovi metodi ausiliari per accedere alle 
  // variabili d'istanza private
   public String  getName() 
      { return name;
      }
   public int  getID() 
      { return ID;
      }

Siccome ora le variabili d'istanza name e ID sono dichiarate private e il loro valore viene inizializzato dal costruttore, non esiste alcun modo di cambiarlo successivamente.
Si noti che vengono forniti i metodi getName()e getID()per accedere ai valori delle variabili d'istanza. Questi metodi sono public, quindi dall'esterno della classe si possono leggere nome e ID di un oggetto  Student ma non si possono modificare.