Paola Magillo, Univestita' di Genova, Corso di Programmazione II per SMID, a.a. 2006-2007.

Lezione 02:

CLASSI E OGGETTI IN JAVA

Linguaggi orientati ad oggetti / Object-Oriented (OO)

In un linguaggio imperativo tradizionale (non O.O.) ci sono:

Esempio: tipo rettangolo in C

Il tipo "rettangolo" e' un record con due interi per lunghezza e larghezza. La funzione "area" prende un rettangolo e ne ritorna l'area. La funzione "print" stampa un rettangolo.

#include <stdio.h>
typedef struct { int length, width; } Rectangle;
int area(Rectangle r)  { return r.length * r.width; }
void print(Rectangle r) 
{ printf("Rettangolo %d x %d\n",r.length,r.width); }

void main()
{
  Rectangle r;
  int a;
  r.length = 3; r.width = 5;
  a = area(r);
  print(r);
  printf("Area = %d\n",a);
}

In un linguaggio object oriented:

Esempio: classe rettangolo in Java

La classe "rettangolo" ha due interi per lunghezza e larghezza, un metodo "area" che ritorna l'area del rettangolo e un metodo "print" che stampa il rettangolo.

class Rectangle
{
  int length, width;
  int area() { return length * width; }
  void print()
  { System.out.println("Rectangle " + length + " x " + width); }

  public static void main(String[] argc)
  {
    Rectangle r = new Rectangle();
    int a;
    r.length = 3; r.width = 5;
    a = r.area();
    r.print();
    System.out.println("Area = " + a);
  }
}

Convenzione: in Java i nomi di classe hanno iniziale maiuscola, gli altri nomi iniziale minuscola. Se un nome e' fatto da piu' parole, le successive hanno iniziale maiuscola (es. "printArea").

Chiamata di metodo

Notazione analoga alla selezione di un campo di un record. Es. se r e' un rettangolo:

L'oggetto su cui si chiama il metodo e' argomento privilegiato del metodo.

All'interno dell'implementazione del metodo l'argomento privilegiato e' lasciato implicito, ma posso anche esplicitarlo usando la parola chiave "this".
Es. nel rettangolo e' equivalente scrivere:

  int area() { return length * width; }
  int area() { return this.length * this.width; }

Usare "this" e' indispensabile quando si vuole ritornare questo oggetto come risultato. Esempio:

  Rectangle bigger(Rectangle r)
  { if (this.area() > r.area()) return this; else return r; }

Che cosa e' una classe

Una classe e' l'analogo di un tipo ma comprende anche le funzioni che agiscono su quel tipo.
E' come un record dove alcuni campi sono variabili ed altri sono funzioni.

Analogia tra classe e modulo:

Che cosa e' un oggetto

Come si dice "un valore di un tipo" cosi' si dice "un oggetto di una classe".
Si dice che un oggetto e' istanza della sua classe.

Un oggetto ha:

Un oggetto e' la "nobilitazione" di un puntatore. L'identita' di un oggetto e' la locazione di memoria dove stanno scritti i suoi dati. I valori contenuti in questa locazione possono cambiare nel tempo ma la locazione e' sempre la stessa.

Visibilita' di attributi e metodi

Classe = Modulo come scatola chiusa. Che cosa si vede da fuori la scatola?
Attributi e metodi possono essere dichiarati come pubblici o privati.

In java devo ripetere le parole chiave "public" e "private" prima di ogni variabile o funzione. Dove non specificato, si intende "public".

Incapsulazione

Si dice che una classe incapsula lo stato dei suoi oggetti quando tutte le variabili che descrivono lo stato sono private, percio' da fuori si possono leggere ed assegnare solo mediante le funzioni definite nella classe.

Buona norma: definire attributi privati con metodi "set" e "get" pubblici. Es. per il rettangolo:

class Rectangle
{
  private int length, width;
  public void setLength (int l)  { length = l; }
  public void setWidth (int w)  { width = w; }
  public int getLength() { return length; }
  public int getWidth() { return width; }
  ...
}
allora non posso piu' scrivere
  r.length = 3; r.width = 5;
ma devo scrivere invece
  r.setLength(3); r.setWidth(5);

Incapsulazione e' particolarmente utile quando i valori dello stato sono soggetti a vincoli di validita', e quindi e' importante evitare che qualcuno li modifichi in modo incontrollato.
Esempio: dimensioni del rettangolo non possono essere negative:

  public void setLength (int l)
  { if (l>=0) length = l; else System.out.println("Negative length!"); }
  public void setWidth (int w)
  { if (w>=0) width = w; else System.out.println("Negative width!"); }

Metodi ausiliari

Metodi privati tipicamente sono funzioni ausiliarie usate solo dentro la classe per facilitare l'implementazione di altri metodi.

Costruttore

Metodo speciale che ogni classe ha, serve per fabbricare nuove istanze. Serve per allocare e inizializzare lo stato di un oggetto al momento della sua creazione. Puo' avere parametri a piacere ma non ha valore di ritorno.

Se non lo scrivo nella dichiarazione della classe, il sistema mette automaticamente un costruttore di default: senza argomenti, crea istanza mettendo tutte le variabili a zero. Si chiama con questa sintassi:

Rectangle r = new Rectangle();

Posso scrivere io un costruttore, anche con parametri. Es. per il rettangolo:

  Rectangle(int l, int w) { length = l;  width = w; }

Si chiama con questa sintassi:

Rectangle r = new Rectangle(5,12);

Se scrivo il mio costruttore, perdo quello di default. Se lo voglio, devo ridefinirlo. Es. nel caso del rettangolo:

  Rectangle() { length = width = 0; }

Nota: una classe puo' avere piu' costruttori con parametri diversi. E' un caso di polimorfismo.

Polimorfismo

In linguaggio tradizionale non posso avere funzioni diverse (cioe' con diversi tipi per argomenti e/o risultato) e con nome uguale.
In linguaggio object oriented si puo'. Si chiama "polimorfismo".

Esempio: nella classe rettangolo ora abbiamo due costruttori.

Polimorfismo vale non solo per i costruttori. Es. nel rettangolo aggiungo funzione "assegna dimensioni" che ha due forme:

  void setSize(int l, int w)  {  length = l; width = w;  }
  void setSize() {  length = width = 0;  }

Naturalmente posso avere funzioni con stesso nome in classi diverse. E questo non e' polimorfismo (e' come avere campi con stesso nome in tipi record diversi).

Attributi e metodi di istanza / di classe

In Java variabili e funzioni di classe sono contrassegnate da parola chiave "static".

Esempio: nella classe "rettangolo" posso mettere variabile di classe

  static int minimumDimension = 0;
e usarla nelle funzioni che assegnano lunghezza e larghezza:
  void setLength(int l)
  { if (l>=minimumDimension) length = l;
    else System.out.println("Invalid length!"); }
se a un certo punto decido di accettare solo rettangoli con lati lunghi almeno 10, basta mettere minimumDimension = 10.

Posso renderla una costante (cioe' il suo valore non potra' piu' essere cambiato) premettendo la parola chiave "final":

  static final int minimumDimension = 0;

Convenzione: i nomi di costanti sono a lettere tutte maiuscole (come le macro in C). Quindi meglio scrivere:

  static final int MINIMUM_DIMENSION = 0;

Gestione della memoria / oggetti e puntatori

Dalla sua costruzione, un oggetto ha sua propria identita' = locazione di memoria dove viene memorizzato.

Linguaggi o.o. tengono una tabella degli oggetti esistenti. L'indice nella tabella e' l'identita' dell'oggetto (OID = object identifier).

Identita' resta inalterata in seguito a modifiche dello stato dell'oggetto (= valori dei suoi attributi).
Modifiche cambiano contenuto della memoria ma non sua locazione.

Esempio:

 1. Rectangle r1 = new Rectangle(2,3);
 2. Rectangle r2 = new Rectangle(5,3);
 3. r1.setLength(5);
 4. Rectangle r3 = r1;
 5. r3.setWidth(4);
 6. r1 = new Rectangle(1,8);
 7. r3 = new Rectangle(10,15);

Un oggetto non e' la variabile che lo denota.

Piu' variabili possono "puntare" lo stesso oggetto (r1 ed r3 dopo istruzione 4).

Un oggetto esiste indipendentemente da quali variabili lo puntanto (dopo istruzione 6 l'oggetto nato come r1 e' puntato solo da r3).

Java, quando un oggetto non e' piu' puntato da nessuna variabile, lo cancella: libera la sua locazione di memoria e la rende disponibile per altri oggetti, cancella il suo OID (es. dopo istruzione 7).
Questo meccanismo si chiama garbage collection (= raccolta della spazzatura).

Garbage collection

In molti linguaggi O.O. incluso Java c'e' garbage collection = gestione automatica di allocazione e deallocazione della memoria dinamica.

La memoria di un oggetto viene allocata alla sua creazione (chiamata al costruttore della classe con istruzione "new" ) e viene deallocata automaticamente quando non e' piu' accessibile attraverso nessuna variabile.

Varie nozioni di uguaglianza tra oggetti

Uguaglianza come identita'

Tra oggetti e' definito l'operatore booleano ==.

Tale operatore confronta gli OID, cioe' le identita' dei due oggetti (le locazioni di memoria).

Risulta vero se hanno la stessa identita' = stessa locazione di memoria. Es. dopo istruzione 4, r1==r3 vale vero.

Se due oggetti hanno stato uguale (stesso contenuto della locazione di memoria) ma non stessa identita' (non stessa locazione) risulta falso. Es. dopo istruzione 3, r1==r2 vale falso.

Uguaglianza come uguale stato

Se voglio funzione che controlli quando due oggetti diversi hanno uguale stato, cioe' che confronti il contenuto delle locazioni di memoria, devo definirla.

Esempio:

  boolean isEqual(Rectangle r) 
  { return ( (r.length==length) && (r.width==width) ); }
Si chiama con r1.isEqual(r2); Uno dei due rettangoli e' argomento privilegiato (asimmetria).

Alternativa con metodo di classe:

  static boolean isEqual(Rectangle r1, Rectangle r2)
  { return ( (r1.length==r2.length) && (r1.width==r2.width) ); }
Simmetria di trattamento fra i due argomenti. Si chiama con Rectangle.isEqual(r1,r2); Oppure con r.isEqual(r1,r2); dove r e' qualsiasi istanza della classe Rectangle (ma quest'ultimo modo e' sconsigliato).

Shallow e deep equality

Essendo gli attributi length e width di tipi base (tipo int), le locazioni di memoria di due rettangoli contengono valori base. Percio' l'operatore == nel corpo di isEqual non pone problemi.

Se invece gli attributi fossero oggetti di una classe, si porrebbe il problema se la loro uguaglianza vada controllata con == direttamente oppure di nuovo con isEqual. Otteniamo due diverse nozioni di uguaglianza:

Esempio: coppie di rettangoli

class RectanglePair
{
  Rectangle first;
  Rectangle second;
  ...
  boolean shallowEqual(RectanglePair rp)
  { return ( (first==rp.first) && (second==rp.second) ); }
  boolean deepEqual(RectanglePair rp)
  { return ( first.isEqual(rp.first) && second.isEqual(rp.second) ); }
}
Shallow: risulta vero se rp e' coppia con esattamente gli stessi rettangoli che ha questa coppia.
Deep: risulta vero se rp e' coppia con rettangoli uguali a quelli che ha questa coppia.

Copia di oggetti

Discorso analogo a quello sulle varie nozioni di uguaglianza. Varie nozioni di copia.

Assegnazione r3=r1 fa puntare la variabile r3 allo stesso oggetto puntato da r1. Dopo, r1==r3 vale vero.

Se voglio creare un altro oggetto uguale a r1, devo scrivere una funzione di copia. Esempio:

 Rectangle copy() { return new Rectangle(length,width); }
Poi posso chiamare r3 = r1.copy(); Dopo, r1==r3 vale falso, ma Rectangle.isEqual(r1,r3) vale vero.

Di solito si scrive un costruttore di copia:

 Rectangle(Rectangle r) { length = r.length; width = r.width; }
Poi posso chiamare r3 = new Rectangle(r1); Costruisce un oggetto copia.

NOTA: Se gli attributi sono a loro volta oggetti, la copia puo' essere shallow o deep.

Il metodo "main"

Java e' linguaggio totalmente object oriented. A parte i tipi base (int, float, double, boolean, char) tutti gli altri tipi sono classi.

Anche il "main" e' una funzione in una classe, che deve avere i seguenti parametri:

  public static void main(String[] args)
Posso "eseguire" qualsiasi classe contenga questa funzione. Quello che viene eseguito e' il main.

Gli argomenti della funzione "main" sono i parametri dati su command-line quando si lancia il programma:
args[0] = il primo parametro
args[1] = il secondo parametro
ecc.
String e' classe predefinita Java per le stringhe di caratteri.

Esempio: sulla command-line accetto una stringa, un intero e un float. Li "decodifico" in questo modo:

class SampleMain
{
  public static void main(String[] args)
  {
    String primo;
    int secondo;
    float terzo;
    if (args.length!=3) 
      System.out.println("il programma va lanciato con tre argomenti");
    else
    {
      primo = args[0];
      System.out.println("Primo argomento stringa = " + primo);
      secondo = Integer.parseInt(args[1]);
      System.out.println("Secondo argomento intero = " + secondo);
      terzo = Float.parseFloat(args[2]);
      System.out.println("Terzo argomento float = " + terzo);
    }
  }
}

Come usare Java in laboratorio

Sorgenti

I sorgenti java possono essere scritti con qualsiasi editor di testi. Importante, se si usa un word processor (come MS-Word) salvare in formato "solo testo", senza formattazione.

La classe XXX va messa in un file chiamato XXX.java (stesso nome della classe con estensione .java).
Attenzione: quando si salva in formato "solo testo", l'editor potrebbe dare automaticamente estensione .txt: bisogna ricordarsi di cambiarla.

Un programma puo' consistere di piu' file (uno per classe). Java rintraccia automaticamente i file che servono, purche' siano nella directory corrente.

Compilare ed eseguire

Java e' linguaggio pensato per generare programmi portabili su piattaforme ddiverse senza essere ricompilati ("compile once, execute everywhere").

In caso di errori run-time, l'interprete riporta esattamente dove (quale funzione, quale linea...).

Posso eseguire su qualunque macchina sia installato l'interprete anche se non ha il compilatore. In rete (http://java.sun.com/) sono disponibili per solaris / windows / linux:

I sorgenti devono essere prima compilati e poi eseguiti.

In laboratorio PC1 - aula 712

La versione di Java installata in laboratorio e' la 1.4.

Come dare gli argomenti da command-line, esempio:

java SampleMain pippo 12 13.8
produce:
Primo argomento stringa = pippo
Secondo argomento intero = 12
Terzo argomento float = 13.8

Librerie Java

Java ha una grande libreria di classi predefinite (Java Foundation Classes, JFC), tra cui:

Queste classi sono divise in package, per usare le classi contenute in un package bisogna "importarlo".

Manuale dei package e delle classi predefinite Java disponibile in rete (http://java.sun.com/reference/api/index.html).