Sei sulla pagina 1di 103

Introduzione alla Programmazione Orientata agli Oggetti in Java

Versione 1.1

Eugenio Polto Stefania Iaffaldano


Ultimo aggiornamento: 1 Novembre 2003

Copyright c 2003 Eugenio Polto, Stefania Iaffaldano. All rights reserved. This document is free; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This document is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this document; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.

Sosteniamo la Cultura Libera: sosteniamo il Free Software


Per la segnalazione di errori e suggerimenti, potete contattarci ai seguenti indirizzi: Web: http://www.eugeniopolito.it E-Mail: eugeniopolito@eugeniopolito.it

A Typeset with L TEX.

Ringraziamenti
Un grazie speciale ad OldDrake, Andy83 e Francesco Costa per il prezioso aiuto che hanno offerto!

Indice
1 Introduzione 8

I Teoria della OOP


2 Le idee fondamentali 2.1 Una breve storia della programmazione . . . . . . . . . . 2.2 I princpi della OOP . . . . . . . . . . . . . . . . . . . . . 2.3 ADT: creare nuovi tipi . . . . . . . . . . . . . . . . . . . 2.4 La classe: implementare gli ADT tramite lincapsulamento 2.5 Loggetto . . . . . . . . . . . . . . . . . . . . . . . . . . 2.6 Le relazioni fra le classi . . . . . . . . . . . . . . . . . . . 2.6.1 Uso . . . . . . . . . . . . . . . . . . . . . . . . . 2.6.2 Aggregazione . . . . . . . . . . . . . . . . . . . . 2.6.3 Ereditariet . . . . . . . . . . . . . . . . . . . . . 2.6.4 Classi astratte . . . . . . . . . . . . . . . . . . . . 2.6.5 Ereditariet multipla . . . . . . . . . . . . . . . . 2.7 Binding dinamico e Polimorsmo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

9
9 9 12 13 15 16 17 17 18 18 22 23 27

II
3

La OOP in Java
Classi e oggetti 3.1 Denire una classe . . . . . . . . . . . . . . . . . . . . . . . 3.2 Garantire lincapsulamento: metodi pubblici e attributi privati 3.3 Metodi ed attributi statici . . . . . . . . . . . . . . . . . . . . 3.4 Costruire un oggetto . . . . . . . . . . . . . . . . . . . . . . 3.5 La classe Persona e loggetto eugenio . . . . . . . . . . . . 3.6 Realizzare le relazioni fra classi . . . . . . . . . . . . . . . . 3.6.1 Uso . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.6.2 Metodi static: un esempio . . . . . . . . . . . . . . 3.6.3 Aggregazione . . . . . . . . . . . . . . . . . . . . . . 3.6.4 Ereditariet . . . . . . . . . . . . . . . . . . . . . . . 3.7 Classi astratte . . . . . . . . . . . . . . . . . . . . . . . . . . 3.8 Interfacce . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3.9 Ereditariet multipla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

29
29 29 29 31 32 34 37 37 39 41 44 52 56 60

Le operazioni sugli oggetti 4.1 Copia e clonazione . . 4.2 Confronto . . . . . . . 4.3 Binding dinamico . . . 4.4 Serializzazione . . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

. . . .

67 67 73 76 79

III APPENDICI
5 Una panoramica sul linguaggio 5.1 Tipi primitivi . . . . . . . . . . 5.2 Variabili . . . . . . . . . . . . . 5.3 Operatori . . . . . . . . . . . . 5.3.1 Operatori Aritmetici . . 5.3.2 Operatori relazionali . . 5.3.3 Operatori booleani . . . 5.3.4 Operatori su bit . . . . . 5.4 Blocchi . . . . . . . . . . . . . 5.5 Controllo del usso . . . . . . . 5.6 Operazioni (Metodi) . . . . . . 5.6.1 Il main . . . . . . . . . 5.6.2 I package . . . . . . . . 5.6.3 Gli stream . . . . . . . . 5.6.4 LI/O a linea di comando 5.6.5 Le eccezioni . . . . . . 5.6.6 Installazione del JDK . . La licenza GNU GPL . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .

83
83 83 84 84 84 85 85 85 86 86 87 89 89 90 91 91 93 94

Elenco delle gure


1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 Una classe in UML. . . . . . . . . . . . . . . . . . . . . . . . . . La classe Persona in UML. . . . . . . . . . . . . . . . . . . . . La relazione duso in UML. . . . . . . . . . . . . . . . . . . . . . La relazione di aggregazione in UML. . . . . . . . . . . . . . . . La relazione di ereditariet in UML. . . . . . . . . . . . . . . . . La classe delle persone come sottoclasse della classe degli animali. Una piccola gerarchia di Animali. . . . . . . . . . . . . . . . . . Una piccola gerarchia di Animali in UML. . . . . . . . . . . . . . Matrimonio fra classe concreta e astratta . . . . . . . . . . . . . Composizione: la classe Studente Lavoratore . . . . . . . . . Studente Lavoratore come aggregazione e specializzazione . . Studente Lavoratore come aggregazione . . . . . . . . . . . . Lex Studente ed ex Lavoratore ora Disoccupato . . . . . . . La classe Studente come sottoclasse di Persona . . . . . . . . . Loggetto primo appena creato . . . . . . . . . . . . . . . . . . . Loggetto primo non ancora creato . . . . . . . . . . . . . . . . . Loggetto eugenio dopo la costruzione . . . . . . . . . . . . . . Esecuzione del programma Applicazione.java . . . . . . . . . Esecuzione del programma Applicazione.java . . . . . . . . . Diagramma UML per interface . . . . . . . . . . . . . . . . . Diagramma UML per implements . . . . . . . . . . . . . . . . . Diagramma UML per il matrimonio fra classe concreta ed interfaccia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Una Pila di numeri interi . . . . . . . . . . . . . . . . . . . . . . Esecuzione di ProvaPila . . . . . . . . . . . . . . . . . . . . . . Gli oggetti primo e secondo dopo la creazione . . . . . . . . . . Gli oggetti primo e secondo dopo lassegnamento secondo = primo; . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Gli oggetti primo e secondo dopo la clonazione . . . . . . . . . . Gli oggetti primo e secondo dopo listruzione secondo.set(16); Un oggetto ha sempre un riferimento implicito ad Object . . . . . Loggetto bill istanza di Cane . . . . . . . . . . . . . . . . . . . base = derivata; . . . . . . . . . . . . . . . . . . . . . . . . . Passaggio dei parametri ad una funzione . . . . . . . . . . . . . . 15 16 17 18 19 20 21 21 23 23 24 25 25 27 33 33 35 51 55 56 59 60 60 64 69 69 71 72 74 75 78 88

Elenco delle tabelle


1 2 Pro e contro delluso di ereditariet e aggregazione . . . . . . . . Tipi primitivi di Java . . . . . . . . . . . . . . . . . . . . . . . . 20 83

1 INTRODUZIONE

Introduzione

Questa trattazione sulla Programmazione Orientata agli Oggetti o Object Oriented Programming (OOP in seguito) in Java nasce dallosservazione che, molto spesso, i manuali sono degli ottimi reference (cfr. [3]) del linguaggio ma non approfondiscono i pochi ma essenziali concetti della OOP; daltra parte nei corsi universitari si presta n troppa attenzione alla teoria senza approfondire gli aspetti implementativi. Quindi si cercher di trattare sia la teoria che la pratica della OOP perch entrambi gli aspetti sono fondamentali per poter scrivere un buon codice orientato agli oggetti. Purtroppo Java viene considerato (in modo errato!) un linguaggio per scrivere soltanto applet o addirittura viene confuso con il linguaggio di script JavaScript : con Java si possono scrivere delle applicazioni standalone che non hanno molto da invidiare (a livello di prestazioni) ai programmi scritti con altri linguaggi OOP pi efcienti come C++. Infatti, attualmente, la Java Virtual Machine (cio lo strato software che si occupa di trasformare il codice intermedio o bytecode in chiamate alle funzioni del Sistema Operativo - syscall) consente di avere delle ottime prestazioni. In [1] potete trovare la versione 1.4 del JDK . importante sottolineare che questo non un manuale sul linguaggio, ma una trattazione sullimpostazione Object Oriented del linguaggio: comunque, se non si conosce la sintassi di Java, consigliabile dare uno sguardo alla sezione 5 dove disponibile un veloce riassunto sulla struttura del linguaggio.

Parte I

Teoria della OOP


In questa parte verranno introdotte e discusse le idee fondamentali della OOP. Se si conoscono gi i princpi ed i meccanismi della OOP si pu passare alla parte successiva, dove si discute come tali concetti sono realizzati in Java.

2
2.1

Le idee fondamentali
Una breve storia della programmazione

La programmazione dei computer esiste ormai da circa 60 anni. Ovviamente in un tempo cos lungo, ha subto notevoli cambiamenti: agli albori larte di programmare consisteva nella programmazione di ogni singolo bit di informazione tramite degli interruttori che venivano accesi e spenti. Quindi il programma era in realt una sequenza di accensioni e spegnimenti di interruttori che producevano il risultato che si voleva, anzi che si sperava di ottenere: pensate cosa poteva comportare lo scambio di una accensione con uno spegnimento o viceversa! Solo intorno al 1960 venne scritto il primo Assembler: questo linguaggio era (ed ) troppo legato alla struttura del microprocessore e soprattutto era (ed ) difcile scrivere un programma con un linguaggio troppo vicino alla macchina e poco amichevole per luomo. Tra il 1969 ed il 1971, Dennis Ritchie scrisse il primo linguaggio di programmazione ad alto livello: il C. Seppure ad un gradino superiore allassembler e soprattutto il codice sorgente di un programma pu essere ricompilato su qualsiasi piattaforma (con qualche modica!), questo linguaggio risulta ancora troppo vicino alla macchina (basti pensare che i Sistemi Operativi sono scritti in C e Assembler). Nel 1983 Bijarne Stroustrup (programmatore presso la compagnia telefonica americana Bell Laboratories) ebbe la necessit di dover simulare un sistema telefonico: i linguaggi allora disponibili si prestavano poco a programmare un sistema cos complesso, cos ebbe lidea di partire dal C per scrivere un nuovo linguaggio che supportasse le classi. Nacque cos il C con oggetti. Lidea di classe era gi nota ed utilizzata in altri linguaggi come Smalltalk . Tuttavia il C con oggetti (in seguito rinominato C++) una estensione del C e quindi ha dei pregi e dei difetti: pro fra i pregi possiamo sicuramente annoverare lefcienza (propria di C) e la portabilit del codice sorgente (con qualche modica) da una archittettura ad unaltra: i compilatori C++ esistono per ogni tipo di piattaforma

2 LE IDEE FONDAMENTALI

10

hardware, pertanto sufciente qualche modica al codice sorgente ed una ricompilazione; contro la gestione della memoria, per esempio, completamente a carico del programmatore e, come si sa, la gestione dei puntatori una fabbrica di errori. inoltre possibile mixare sia codice C che codice C++, ottenendo cos un codice non molto pulito dal punto di vista della OOP: supponiamo di volere confrontare due variabili a e b. Il codice corretto il seguente:
if (a == b) {...}

Ma basta omettere un = per ottenere un assegnamento:


if (a = b) {...}

se lassegnamento va a buon ne, viene eseguito il blocco di istruzioni. Un errore del genere pu essere frequente: se non usato bene, il C++ rischia di essere un boomerang per il programmatore. Nei primi mesi del 1996 venne presentata la prima versione del linguaggio Java che introduceva non poche novit: macchina virtuale anche se non era un concetto nuovo nellinformatica (gi IBM aveva fatto delle sperimentazioni sulle Virtual Machine in un Sistema Operativo proprietario), lidea di sfruttare una Macchina Virtuale o Java Virtual Machine - JVM che si interpone fra il linguaggio intermedio bytecode ed il linguaggio macchina della architettura sottostante era una novit assoluta. Tale idea stata ultimamente ripresa da una nota Software House con un progetto denominato .NET . . . portabilit grazie al concetto di Virtual Machine sufciente compilare una sola volta il programma per poi eseguire il programma .class in formato di bytecode su qualsiasi altra piattaforma; per C++ vale solo una portabilit di codice sorgente e non di programma eseguibile; esecuzione di programmi nei browser (applet) si pu scrivere una unica applicazione che venga eseguita sia in modo nativo che allinterno di un browser web; gestione automatica della memoria questo sicuramente uno degli aspetti pi importanti di Java. Ci preoccupiamo solo della costruzione di un oggetto, perch la distruzione completamente gestita dalla JVM tramite il Garbage

2 LE IDEE FONDAMENTALI

11

Collector: un oggetto non pi utilizzato viene automaticamente distrutto. Inoltre in Java esiste solo il concetto di riferimento ad un oggetto che comporta una gestione semplice della notazione (si pensi alla notazione . o -> di C++ a seconda che un metodo sia richiamato su una variabile oggetto / reference o su un puntatore). Lassenza della gestione diretta dei puntatori consente di produrre un codice sicuro. Per un confronto fra Java e C++ cfr. [4].

2 LE IDEE FONDAMENTALI

12

2.2

I princpi della OOP

La OOP una evoluzione naturale dei linguaggi di programmazione: essa nasce con lo scopo preciso di simulare e modellare la realt. I princpi su cui si basa la OOP sono semplici ma molto potenti:

Denire nuovi tipi di dati. Incapsulare i valori e le operazioni. Riusare il codice esistente (ereditariet). Fornire il polimorsmo.
Come vedremo, nella OOP non si fa differenza fra valori ed operazioni: semplicemente si parla di Tipo di dato che ingloba le due entit in ununica struttura. Quindi necessario denire un nuovo Tipo di dato. altrettanto necessario accedere ad un valore di un tipo di dato: come vedremo questo fattibile tramite il meccanismo di incapsulamento. Un altro cardine della OOP il riuso del codice: cio utilizzare del codice esistente per poterlo specializzare. Il polimorsmo si rende necessario, come vedremo, in una gerarchia di ereditariet.

2 LE IDEE FONDAMENTALI

13

2.3

ADT: creare nuovi tipi

Un Tipo di Dato Astratto o Abstract Data Type - ADT , per denizione, un nuovo tipo di dato che estende i tipi nativi forniti dal linguaggio di programmazione. Un ADT caratterizzato da un insieme di:

dati;

operazioni che agiscono sui dati, leggengoli/scrivendoli;


Fin qui niente di nuovo: anche i linguaggi procedurali, come per esempio C, consentono di denire un ADT . Ma, mentre per tali linguaggi chiunque pu avere accesso ai dati e modicarli, i linguaggi Object Oriented ne garantiscono la loro riservatezza. Supponiamo infatti di voler denire in C (non preoccuparsi della sintassi) un ADT Persona cio una struttura dati che mantenga le informazioni (dati) di una Persona, come, per esempio, il nome, il cognome e la data di nascita e che consenta di creare e stampare le informazioni di una persona (operazioni):
/* Struttura dati per mantenere la data di nascita */ struct Data { int giorno; int mese; int anno; }; /* Struttura dati per mantenere le info. della persona */ struct Persona { struct Data *data_di_nascita; char *nome; char *cognome; }; /* Setta le info. della persona */ void creaPersona(struct Persona *persona) { persona->data_di_nascita->giorno = 31; persona->data_di_nascita->mese = 12; persona->data_di_nascita->anno = 1976; persona->nome = "Eugenio"; persona->cognome = "Polito"; }

2 LE IDEE FONDAMENTALI
/* Stampa le info. della persona */ void stampaDati(struct Persona *persona) { printf("Mi chiamo %s %s e sono nato il %i-%i-%i \n", persona->nome, persona->cognome, persona->data_di_nascita->giorno, persona->data_di_nascita->mese, persona->data_di_nascita->anno); } /* crea un puntatore alla struttura e lo inizializza; quindi stampa le info. */ int main() { struct Persona *io; creaPersona(io); stampaDati(io); return 0; }

14

Se eseguiamo questo programma, otteniamo il seguente output:


Mi chiamo Eugenio Polito e sono nato il 31-12-1976

Proviamo adesso a modicare il main nel seguente modo:


int main() { struct Persona *io; creaPersona(io); io->data_di_nascita->mese = 2; stampaDati(io); return 0; }

Questa volta loutput :


Mi chiamo Eugenio Polito e sono nato il 31-2-1976

Cio le mie informazioni private sono state modicate con lassegnamento:


io->data_di_nascita->mese = 2

2 LE IDEE FONDAMENTALI

15

2.4

La classe: implementare gli ADT tramite lincapsulamento

La classe consente di implementare gli ADT attraverso il meccanismo di incapsulamento: i dati devono rimanere privati insieme allimplementazione e solo linterfaccia delle operazioni resa pubblica allesterno della classe. Questo approccio fondamentale per garantire che nessuno possa accedere alle informazioni della classe e quindi, dal punto di vista del programmatore, una garanzia per non fare errori nella stesura del codice: basti pensare allesempio dellADT Persona visto prima. Se i dati fossero stati privati non avrei potuto liberamente modicare la data di nascita nel main. Quindi, ricapitolando, una classe implementa un ADT (un sinonimo di classe proprio tipo) attraverso il meccanismo di incapsulamento. La descrizione di una classe deve elencare: i dati (o attributi): contengono le informazioni di un oggetto; le operazioni (o metodi): consentono di leggere/scrivere gli attributi di un oggetto; Quando si scrive una applicazione buona norma iniziare con la progettazione dellapplicazione stessa; Grady Booch identica i seguenti obiettivi in questa fase:

identicare le classi; identicare le funzionalit di queste classi; trovare le relazioni fra le classi;
Questo processo non pu che essere iterativo. Nella fase di progettazione si usa un formalismo graco per rappresentare le classi e le relazioni fra di esse: lUML - Unied Modeling Language. In UML una classe si rappresenta cos:
Qui va il nome della classe Qui vanno messi gli attributi Qui vanno messi i metodi

Figura 1: Una classe in UML.

2 LE IDEE FONDAMENTALI
Quindi la classe Persona in UML cos rappresentata:

16

Figura 2: La classe Persona in UML.

2.5

Loggetto

Che cos quindi un oggetto? Per denizione, diciamo che un oggetto una istanza di una classe. Quindi un oggetto deve essere conforme alla descrizione di una classe. Un oggetto pertanto contraddistinto da: 1. attributi; 2. metodi; 3. identit; Allora se abbiamo una classe Persona, possiamo creare loggetto eugenio che una istanza di Persona. Tale oggetto avr degli attributi come, per esempio, nome, cognome e data di nascita; avr dei metodi come creaPersona(...) , stampaDati(...), etc. Inoltre avr una identit che lo contraddistingue da un eventuale fratello gemello, diciamo pippo (anche lui ovviamente istanza di Persona). Per il meccanismo di incapsulamento un oggetto non deve mai manipolare direttamente i dati di un altro oggetto: la comunicazione deve avvenire tramite messaggi (cio chiamate a metodi). I client devono inviare messaggi ai server! Quindi nellesempio di prima: se il fratello gemello pippo, in un momento di amnesia, vuole sapere quando nato eugenio deve inviargli un messaggio, cio deve richiamare un metodo ottieniDataDiNascita(...) . Quindi, ricapitolando, possiamo dire che:

la classe una entit statica cio a tempo di compilazione; loggetto una entit dinamica cio a tempo di esecuzione (run time);
Nella sezione 4 vedremo come gli oggetti vengono gestiti in Java.

2 LE IDEE FONDAMENTALI

17

2.6

Le relazioni fra le classi

Un aspetto importante della OOP la possibilit di denire delle relazioni fra le classi per riuscire a simulare e modellare il mondo che ci circonda : uso: una classe pu usare oggetti di unaltra classe; aggregazione: una classe pu avere oggetti di unaltra classe; ereditariet: una classe pu estendere unaltra classe. Vediamole in dettaglio singolarmente. 2.6.1 Uso

Luso o associazione la relazione pi semplice che intercorre fra due classi. Per denizione diciamo che una classe A usa una classe B se: - un metodo della classe A invia messaggi agli oggetti della classe B, oppure - un metodo della classe A crea, restituisce, riceve oggetti della classe B. Per esempio loggetto eugenio (istanza di Persona) usa loggetto phobos (istanza di Computer) per programmare: quindi loggetto eugenio ha un metodo (diciamo programma(...)) che usa phobos (tale oggetto avr per esempio un metodo scrivi(...)). Osserviamo ancra che in questo modo lincapsulamento garantito: infatti eugenio non pu accedere direttamente agli attributi privati di phobos, come ram o bus ( il Sistema Operativo che gestisce tali risorse). Questo discorso pu valere per Linux che nasconde bene le risorse, ma non pu valere per altri Sistemi Operativi che avvertono lavvenuto accesso a parti di memoria riservate al kernel ed invitano a resettare il computer. . . In UML questa relazione si rappresenta cos:

Figura 3: La relazione duso in UML. Per la realizzazione di questa relazione in Java vedere la sezione 3.6.1.

2 LE IDEE FONDAMENTALI
2.6.2 Aggregazione

18

Per denizione si dice che una classe A aggrega (contiene) oggetti di una classe B quando la classe A contiene oggetti della classe B. Pertanto tale relazione un caso speciale della relazione di uso. Sugli oggetti aggregati sar possibile chiamare tutti i metodi, ma ovviamente non sar possibile accedere agli attributi (lincapsulamento continua a regnare!). N.B.: la relazione di aggregazione viene anche chiamata relazione has-a o ha-un. Ritorniamo al nostro esempio della classe Persona: come si detto una persona ha una data di nascita. Risulta pertanto immediato e spontaneo aggregare un oggetto della classe Data nella classe Persona! In UML la relazione A aggrega B si disegna cos:

Figura 4: La relazione di aggregazione in UML. Notare che il rombo attaccato alla classe che contiene laltra. Un oggetto aggregato semplicemente un attributo! Vi rimando alla sezione 3.6.3 per la realizzazione in Java di tale relazione. 2.6.3 Ereditariet

Questa relazione (anche detta inheritance o specializzazione) sicuramente la pi importante perch rende possibile il riuso del codice. Si dice che una classe D (detta la classe derivata o sottoclasse) eredita da una classe B (detta la classe base o superclasse) se gli oggetti di D formano un sottoinsieme degli oggetti della classe base B. Tale relazione anche detta relazione is-a o -un. Inoltre si dice che D un sottotipo di B. Da questa denizione possiamo osservare che la relazione di ereditariet la relazione binaria di sottoinsieme , cio: D Sappiamo che

una relazione che gode della propriet transitiva: C B A C A

Pertanto la relazione di ereditariet transitiva! Nasce spontaneo domandarsi perch vale e non vale . Il motivo presto detto: la relazione una relazione dordine fra insiemi, quindi gode di tre propriet:

2 LE IDEE FONDAMENTALI
1. riessiva: A

19

A A A B B B A C A

2. antisimmetrica: A B B 3. transitiva: C

Ma riettendo sul concetto di ereditariet, afnch si verichi la 1. dovrebbe succedere che una classe erediti da se stessa, cio la classe dovrebbe essere una classe derivata da se stessa: impossibile! Analogamente la propriet 2. dice che una classe una classe base ed una classe derivata allo stesso tempo: anche questo impossibile! Quindi vale solo la 3. D eredita da B, in UML, si disegna cos:

Figura 5: La relazione di ereditariet in UML. Vediamo adesso perch con lereditariet si ottiene il riuso del codice. Consideriamo una classe base B che ha un metodo f(...) ed una classe derivata D che eredita da B. La classe D pu usare il metodo f(...) in tre modi: lo eredita: quindi f(...) pu essere usato come se fosse un metodo di D; lo riscrive (override): cio si da un nuovo signicato al metodo riscrivendo la sua implementazione nella classe derivata, in modo che tale metodo esegua una azione diversa; lo estende: cio richiama il metodo f(...) della classe base ed aggiunge altre operazioni. immediato, pertanto, osservare che la classe derivata pu risultare pi grande della classe base relativamente alle operazioni ed agli attributi. La classe derivata non potr accedere agli attributi della classe base, anche se li eredita, proprio per garantire lincapsulamento. Tuttavia, come vedremo, possibile avere un accesso

2 LE IDEE FONDAMENTALI

20

controllato agli attributi della classe base da una classe derivata. importante notare che lereditariet pu essere simulata con laggregazione (cio is-a diventa has-a)! Ovviamente ci sono dei pro e dei contro, che possiamo riassumere cos: Ereditariet Pro polimorsmo e binding dinamico Contro legame stretto fra classe base e derivata Aggregazione Pro chiusura dei moduli Contro riscrittura dei metodi nella classe derivata

Tabella 1: Pro e contro delluso di ereditariet e aggregazione Java non supporta linheritance multiplo quindi necessario ricorrere allaggregazione (vedere la sottosezione successiva 2.6.5). Riprendiamo la classe Persona: pensandoci bene tale classe deriva da una classe molto pi grande, cio la classe degli Animali:
Animali Persone

     Cani                                                                  

Figura 6: La classe delle persone come sottoclasse della classe degli animali. Quindi ogni Persona un Animale; un oggetto di tipo Persona, come eugenio, anche un Animale. Cos come il mio cane bill un oggetto di tipo Cane ed anche lui fa parte della classe Animale. Riettiamo adesso sulle operazioni (metodi) che pu fare un Animale: un animale pu mangiare, dormire, cacciare, correre, etc. Una Persona un Animale: di conseguenza eredita tutte le operazioni che pu fare un Animale. Lo stesso vale per la classe Cane. Ma sorge a questo punto una domanda: una Persona mangia come un Cane? La risposta ovviamente No! Infatti una Persona per poter mangiare usa le proprie mani, a differenza del Cane che fa tutto con la bocca e le zampe: quindi loperazione del mangiare deve essere ridenita nella classe Persona!

2 LE IDEE FONDAMENTALI

21

Inoltre possiamo pensare a cosa possa fare in pi una Persona rispetto ad un Animale: pu parlare, costruire, studiare, fare le guerre, inquinare... etc. Quindi nella classe derivata si possono aggiungere nuove operazioni! Si detto precedentemente che la relazione di ereditariet transitiva: verichiamo quanto detto con un esempio. Pensiamo ancora alle classe Animale: come ci insegna Quark (. . . e la Scuola Elementare. . . ) il mondo Animale composto da sottoclassi come la classe dei Mammiferi, degli Anfbi, degli Insetti, etc. La classe dei Mammiferi a sua volta composta dalla classe degli Esseri Umani, dei Cani, delle Balene, etc.:
Animali
Cani                         

Mammiferi
Umani                              Esseri

                    Non    Alati                     


Alati

Insetti

Figura 7: Una piccola gerarchia di Animali. Ripensiamo adesso al Cane: tale classe una sottoclasse di Mammifero che a sua volta una sottoclasse di Animale, in UML:

Figura 8: Una piccola gerarchia di Animali in UML. Pertanto ogni Cane un Mammifero e, poich ogni Mammifero un Animale, concludiamo che un Cane un Animale! Pertanto ogni Cane potr fare ogni operazione denita nella classe Animale. Con questo esempio abbiamo anche introdotto il concetto di gerarchia di classi, che, per denzione, un insieme di classi che estendono una classe base comune.

2 LE IDEE FONDAMENTALI
2.6.4 Classi astratte

22

Riprendiamo lesempio di Figura 8 ed esaminiamo i metodi della classe base Animale: consideriamo, per esempio, loperazione comunica(...). Se pensiamo ad un Cane tale operazione viene eseguita attraverso le espressioni della faccia, del corpo, della coda. Un Essere Umano pu espletare tale operazionr in modo diverso: attraverso i gesti, le espressioni facciali, la parola. Un Delfino, invece, comunica attraverso le onde sonore. Allora che cosa signica tutto questo? Semplicemente stiamo dicendo che loperazione comunica(...) non sappiamo come pu essere realizzata nella classe base! Un discorso analogo pu essere fatto per loperazione mangia(...). In sostanza sappiamo che questi metodi esistono per tutte le classi che derivano da Animale e che sono proprio tali classi a sapere come realizzare (implementare) questi metodi. I metodi come comunica(...), mangia(...) etc., si dicono metodi astratti o metodi differiti: cio si dichiarano nella classe base, ma non vengono implementati; saranno le classi derivate a sapere come implementare tali operazioni. Una classe che ha almeno un metodo astratto si dice classe astratta e deve essere dichiarata tale. Una classe astratta pu anche contenere dei metodi non astratti (concreti)! Nella sezione 3.7 vedremo come dichiararle e usarle in Java. Attraverso delle considerazioni siamo arrivati a denire la classe Animale come classe astratta. Riettiamo un momento sul signicato di questa denizione: creare oggetti della classe Animale serve a ben poco, proprio perch tale classe n troppo generica per essere istanziata. Piuttosto pu essere usata come un contenitore di comportamenti (operazioni) comuni che ogni classe derivata sa come implementare! Questo un altro punto fondamentale della OOP: bene individuare le operazioni comuni per poterle posizionare al livello pi alto nella gerarchia di ereditariet. La classe Cane, Delfino etc., implementeranno ogni operazione astratta, proprio perch ognuna di queste classi sa come fare per svolgere le operazioni ereditate dalla classe base.

2 LE IDEE FONDAMENTALI
2.6.5 Ereditariet multipla

23

Nella sottosezione 2.6.3 si parlato della relazione di ereditariet fra due classi. Questa relazione pu essere estesa a pi classi. Esistono tre forme di ereditariet multipla: matrimonio fra una classe concreta ed una astratta: per esempio:

Figura 9: Matrimonio fra classe concreta e astratta Quindi Stack la classe astratta che denisce le operazioni push(...), pop(...), etc. La classe array serve per la manipolazione degli array. Pertanto la Pila implementa le operazioni dello Stack e le richiama su un array. duplice sottotipo: una classe implementa due interfacce ltrate. composizione: una classe estende due o pi classi concrete. proprio questo caso che genera alcuni problemi. Consideriamo il classico esempio (cfr. [2]) della classe Studente Lavoratore: questa classe estende la classe Studente e Lavoratore (entrambe estendono la classe Persona):

Figura 10: Composizione: la classe Studente Lavoratore

2 LE IDEE FONDAMENTALI

24

La classe Persona ha degli attributi, come nome, cognome, data di nascita etc., e dei metodi, come, per esempio, mangia(...). Sia la classe Studente che la classe Lavoratore estendono la classe Persona, quindi erediteranno sia attributi che metodi. Supponiamo di creare loggetto eugenio come istanza di Studente Lavoratore e richiamiamo su di esso il metodo mangia(...). Purtroppo tale metodo esiste sia nella classe Studente che nella classe Lavoratore: quale metodo sar usato? Nessuno dei due perch il compilatore riporter un errore in fase di compilazione! Analogamente i membri saranno duplicati perch saranno ereditati da entrambe le classi (Studente e Lavoratore): eugenio si ritrover con due nomi, due cognomi e due date di nascita. I progettisti di Java, proprio per evitare simili problemi, hanno deciso di non supportare la composione come forma di ereditariet multipla. Per, come si detto nella sezione 2.6.3, lereditariet pu essere simulata con laggregazione, pertanto il diagramma UML di Figura 10 pu essere cos ridisegnato:

Figura 11: Studente Lavoratore come aggregazione e specializzazione Adesso lo Studente Lavoratore eredita un solo metodo mangia(...), dorme(...), etc., cos come avr un solo nome, cognome, etc. Se eugenio deve lavorare(. . . ) richiamer il metodo omonimo sulloggetto aggregato istanza di Lavoratore. Questo esempio porta ad unaltra riessione importante: ma eugenio sar sempre uno Studente? Si spera di no. . . Prima o poi nir di studiare! Come si detto in Tabella 1, lereditariet ha lo svantaggio di stabilire un legame troppo forte tra classe base e derivata. Ci signica che loggetto eugenio (che magari continua a vivere nella societ in qualit di Lavoratore), anche quando non sar pi uno Studente, potr invocare il metodo faiLaFilaInSegreteria(...) o ricopiaAppunti(...) , perch con-

2 LE IDEE FONDAMENTALI

25

tinua ad essere uno Studente, secondo la gerarchia di Figura 11! Risulta immediato cambiare nuovamente lereditariet con laggregazione:

Figura 12: Studente Lavoratore come aggregazione In questo modo, ad esempio, il metodo faiLaFilaInSegreteria(...) viene richiamato sulloggetto aggregato istanza di Studente. Quando eugenio non sar pi Studente, loggetto aggregato istanza di Studente verr eliminato (tanto un semplice attributo!). Se poi malauguratamente eugenio perde il proprio lavoro, non aggrega pi la classe Lavoratore: pu comunque aggregare una nuova classe, come per esempio Disoccupato:

Figura 13: Lex Studente ed ex Lavoratore ora Disoccupato Perch abbiamo aggregato una nuova classe (Disoccupato)? Se guardiamo la Figura 11 si ha (per la transitivit della relazione di ereditariet): Studente Lavoratore Studente Persona Studente Lavoratore Persona. Quindi ogni Studente Lavoratore pu invocare il metodo mangia(...) della classe Persona (lo eredita). Analogamente, in Figura 11, vediamo che sia Studente che Lavoratore ereditano da Persona. Quindi uno Studente Lavoratore pu invocare il metodo mangia(...) sia sulloggetto aggregato istanza di Studente che sulloggetto aggregato istanza di Lavoratore.

2 LE IDEE FONDAMENTALI

26

Ma se un oggetto di classe Studente Lavoratore termina di studiare e perde il lavoro (cio eliminiamo gli attributi, oggetti di tipo Studente e Lavoratore) potr continuare a mangiare? Risposta: No! Ecco spiegato il motivo per cui stata aggregata una nuova classe in Studente Lavoratore. Ricapitolando:

Lereditariet multipla sottoforma di composizione pu essere modellata


con laggregazione e con lereditariet singola. bene usare questa combinazione per non incorrere in problemi seri durante la stesura del codice.

Usare lereditariet solo quando il legame fra la classe base e la classe


derivata per sempre, cio dura per tutta la vita degli oggetti, istanze della classe derivata. Se tale legame non duraturo meglio usare laggregazione al posto della specializzazione.

2 LE IDEE FONDAMENTALI

27

2.7

Binding dinamico e Polimorsmo

La parola polimorsmo deriva dal greco e signica letteralmente molte forme. Nella OOP tale termine si riferisce ai metodi: per denizione, il polimorsmo la capacit di un oggetto, la cui classe fa parte di una gerarchia, di chiamare la versione corretta di un metodo. Quindi il polimorsmo necessario quando si ha una gerarchia di classi. Consideriamo il seguente esempio:

Figura 14: La classe Studente come sottoclasse di Persona Nella classe base Persona denito il metodo calcolaSomma(...) , che, per esempio, esegue la somma sui naturali 2+2 e restituisce 5 (in 3 vedremo come passare argomenti ad un metodo e restituire valori); la classe derivata Studente invece riscrive il metodo calcolaSomma(...) ed esegue la somma sui naturali 2+2 in modo corretto, restituendo 4. N.B. Il metodo deve avere lo stesso nome, parametri e tipo di ritorno in ogni classe, altrimenti non ha senso parlare di polimorsmo. Creiamo adesso loggetto eugenio come istanza di Studente ed applichiamo il metodo calcolaSomma(...) . Loggetto eugenio istanza di Studente, quindi verr richiamato il metodo di tale classe ed il risulato sar 4. Supponiamo adesso di modicare il tipo di eugenio in Persona (non ci preoccupiamo del dettaglio del linguaggio, vedremo in 4.3 come possibile farlo in Java): cambiare il tipo di un oggetto, istanza di una classe derivata, in tipo della classe base possibile ed proprio per questo motivo che necessario il polimorsmo; tuttavia questa conversione o cast comporta una perdita di propriet delloggetto perch una classe base ha meno informazioni (metodi ed attributi) della classe derivata. A questo punto richiamiamo il metodo calcolaSomma(...) sulloggetto eugenio. Stavolta verr richiamato il metodo della classe base: il tipo di eugenio Persona e quindi il risultato 5! Ma come possibile invocare un metodo sullo stesso oggetto in base al suo tipo? Ovviamente questo non pu essere fatto durante la compilazione del programma, perch il metodo da invocare deve dipendere dal tipo delloggetto durante

2 LE IDEE FONDAMENTALI

28

lesecuzione del programma! Per rendere possibile questo il compilatore deve fornire il binding dinamico, cio il compilatore non genera il codice per chiamare un metodo durante la compilazione (binding statico), ma genera il codice per calcolare quale metodo chiamare su un oggetto in base alle informazioni sul tipo delloggetto stesso durante lesecuzione (run-time) del programma. Questo meccanismo rende possibile il polimorsmo puro (o per sottotipo): il messaggio che stato inviato alloggetto eugenio era lo stesso, per ci che cambiava era la selezione del metodo corretto da invocare che dipendeva quindi dal tipo a run-time delloggetto. Ecco come viene invocato correttamente un metodo in una gerarchia di ereditariet (supponiamo che il metodo venga richiamato su una sottoclasse, p.e. Studente):

la sottoclasse controlla se ha un tale metodo; in caso affermativo lo usa,


altrimenti:

la classe padre si assume la responsabilit e cerca il metodo. Se lo trova


lo usa, altrimenti sar la sua classe padre a predendere la responsabilit di gestirlo. Questa catena si interrompe se il metodo viene trovato, e sar tale classe ad invocarlo, altrimenti, se non viene trovato, il compilatore segnala lerrore in fase di compilazione. Pertanto lo stesso metodo pu esistere su pi livelli della gerarchia di ereditariet. Il polimosmo puro non lunica forma di polimorsmo: polimorsmo ad hoc (overloading) un metodo pu avere lo stesso nome ma parametri diversi: il compilatore sceglie la versione corretta del metodo in base al numero ed al tipo dei parametri. Il tipo di ritorno non viene usato per la risoluzione, cio se si ha un metodo con gli stessi argomenti e diverso tipo di ritorno, il compilatore segnala un errore durante la compilazione. Tale meccanismo quindi risolto a tempo di compilazione. N.B. Il polimorsmo puro invece si applica a metodi con lo stesso nome, numero e tipo di parametri e tipo di ritorno e viene risolto a run-time. polimorsmo parametrico la capacit di eseguire delle operazioni su un qualsiasi tipo: questa tipologia non esiste in Java (ma pu essere simulato cfr. 3.9), perch necessita del supporto di classi parametriche. Per la realizzazione di questo meccanismo in C++ cfr. [2].

29

Parte II

La OOP in Java
In questa parte vedremo come vengono realizzati i concetti della OOP in Java.

3
3.1

Classi e oggetti
Denire una classe

La denizione di una classe in Java avviene tramite la parola chiave class seguita dal nome della classe. Afnch una classe sia visibile ad altre classi e quindi istanziabile necessario denirla public:
public class Prima { }

N.B. In Java ogni classe deriva dalla classe base cosmica Object: quindi anche se non lo scriviamo esplicitamente, il compilatore si occupa di stabilire la relazione di ereditariet fra la nostra classe e la classe Object! Le parentesi { e } individuano linzio e la ne della classe ed, in generale, un blocco di istruzioni. bene usare la lettera maiuscola iniziale per il nome della classe; inoltre il nome della classe deve essere lo stesso del nome del le sico, cio in questo caso avremmo Prima.java (vedere la sezione 5). Afnch una classe realizzi un ADT (cfr. sezione 2.3) necessario denire i dati e le operazioni.

3.2

Garantire lincapsulamento: metodi pubblici e attributi privati

Come si detto (cfr. sezione 2.4), uno dei princpi della OOP lincapsulamento: quindi necessario denire dati (membri nella terminologia Java) privati e le operazioni (detti anche metodi in Java) pubbliche. Deniamo lADT Persona della sezione 2.3 in Java; per adesso supponiamo che la persona abbia tre attributi nome, cognome, anni e due metodi creaPersona(...) e stampaDati(...):

3 CLASSI E OGGETTI
public class Persona { /* questo metodo inizializza gli attributi nome, cognome ed anni */ public void creaPersona(String n,String c,int a) { nome = n; cognome = c; anni = a; } // questo metodo stampa gli attributi public void stampaDati() { System.out.println("Nome: "+nome); System.out.println("Cognome: "+cognome); System.out.println("Et: "+anni); } // attributi private String nome; private String cognome; private int anni; }

30

Abbiamo denito quindi gli attributi private ed i metodi public: lincapsulamento garantito! Riettiamo un attimo sulla sintassi: attributi: la dichiarazione di un attributo richiede il modicatore di accesso (usare sempre private!), il tipo dellattributo (String, int, etc. che pu essere sia un tipo primitivo che una classe - vedere la sezione 5 per i tipi primitivi del linguaggio) ed il nome dellattibuto (anni, nome, etc.); metodi: la dichiarazione di un metodo richiede invece: il modicatore di accesso (pu essere sia public che private, in questo caso per il metodo non potr essere invocato da un oggetto - bene usarlo per funzioni di servizio), il tipo di ritorno che pu essere: o un tipo primitivo o una classe o void: cio il metodo non restituisce alcuna informazione. Se il

3 CLASSI E OGGETTI

31

tipo di ritorno diverso da void, deve essere usata listruzione return valore_da_restituire; come ultima istruzione del metodo; il valore_da_restituire deve avere un tipo in match col tipo di ritorno (devono essere gli stessi tipi). Segue quindi il nome del metodo e fra parentesi tonde si specicano gli argomenti (anche detti rma del metodo): anche essi avranno un tipo (primitivo o una classe) ed un nome; pi argomenti vanno separati da una virgola. La lista degli argomenti pu essere vuota. Nel nostro caso gli argomenti passati al metodo creaPersona(...) servono per inizializzare gli attributi: con lassegnamento nome = n; stiamo dicendo che allattributo nome stiamo assegnandogli la variabile n. // viene usato per i commenti su una singola linea, mentre /* e */ vengono usati per scrivere commenti su pi linee (il compilatore ignora i commenti). Il metodo println(...) della classe System usato per scrivere loutput a video e + serve per concatenare stringhe (vedere la sezione 5). La classe, cos come stata denita, non serve ancora a molto: vogliamo creare oggetti che siano istanze di questa classe, sui quali possiamo invocare dei metodi. Dove andremo ad istanziare un generico oggetto di tipo Persona? Prima di rispondere a questa domanda affrontiamo un altro discorso importante che ci servir per capire alcune cose:

3.3

Metodi ed attributi statici

Gli attributi static sono utili quando necessario condividerli fra pi oggetti, quindi anzich avere pi copie di un attributo che dovr essere lo stesso per tutti gli oggetti istanza della stessa classe, esso viene inzializzato una volta per tutte e posto nella memoria statica. Un simile attributo avr la stessa vita del programma, per esempio possiamo immaginare che in una classe Data utile avere memorizzato un array (cfr. 5) dei mesi dellanno:
private static String[] mesi = {"Gen","Feb","Mar", "Apr","Mag","Giu", "Lug","Ago","Set", "Ott","Nov","Dic"};

Tale array sar condiviso fra tutti gli oggetti di tipo Data. Siccome tale array, in realt costante, risulta comodo denirlo tale: in Java si usa la parola nal per denire un attributo costante:
private static final String[] mesi = {"Gen","Feb","Mar", "Apr","Mag","Giu",

3 CLASSI E OGGETTI
"Lug","Ago","Set", "Ott","Nov","Dic"};

32

Quindi mesi non modicabile! Allo stesso modo possibile denire un metodo static: un tale metodo pu essere richiamato senza la necessit di istanziare la classe (vedere la sottosezione 3.6.2 per un esempio). In Java esiste un punto di inizio per ogni programma, dove poter creare loggetto istanza della classe ed invocare i metodi: il metodo main(...). Esso viene richiamato prima che qualsiasi oggetto stato istanziato, pertanto necessario che sia un metodo statico. La sua dichiarazione, che deve comparire in una sola classe, la seguente:
public static void main(String args[]) { }

Quindi public per poter essere visto allesterno, static per il motivo che si diceva prima, non ha alcun tipo di ritorno, accetta degli argomenti di tipo String che possono essere passati a linea di comando.

3.4

Costruire un oggetto

Possiamo adesso affrontare la costruzione di un oggetto. In Java un oggetto viene costruito con il seguente assegnamento:
Prova primo = new Prova();

Analizziamo la sintassi: stiamo dicendo che il nostro oggetto di nome primo una istanza della classe Prova e che lo stiamo costruendo, con loperatore new, attraverso il costruttore Prova(). Loggetto che viene cos creato posto nella memoria heap (o memoria dinamica), la quale cresce e dimunisce a run-time, ogni volta che un oggetto creato e distrutto. N.B. Mentre la costruzione la controlliamo noi direttamente, la distruzione viene gestita automaticamente dalla JVM : quando un oggetto non viene pi usato, la JVM si assume la responsabilit di eliminarlo, senza che noi ce ne possiamo accorgere, tramite il meccanismo di Garbage Collection! Lassegnamento dice che la variabile oggetto primo un riferimento ad un oggetto, istanza della classe Prova. Il concetto di riferimento importante: molti pensano che Java non abbia i puntatori: sbagliato! Java non ha la sintassi da puntatore ma ne ha il comportamento.

3 CLASSI E OGGETTI

33

Infatti una variabile oggetto serve per accedere alloggetto e non per memorizzarne le sue informazioni!; pertanto un oggetto di Java si comporta come una variabile puntatore di C++. La gestione dei puntatori viene completamente nascosta al programmatore, il quale pu solo usare riferimenti agli oggetti. La situazione dopo la costruzione delloggetto primo la seguente:
primo

Prova

Figura 15: Loggetto primo appena creato Sottolineiamo che con la seguente scrittura:
Prova primo;

non stato creato alcun oggetto, infatti si sta semplicemente dicendo che loggetto primo che verr creato sar una istanza di Prova o di una sua sottoclasse; si ha questa situazione:
primo

Prova

Figura 16: Loggetto primo non ancora creato cio primo non ancora un oggetto in quanto non fa riferimento a niente! La costruzione dovr avvenire con listruzione:
primo = new Prova();

Come si detto prima, il metodo Prova() il costruttore delloggetto, cio il metodo che si occupa di inizializzare gli attributi delloggetto. Essendo un metodo pu essere overloadato, cio pu essere usato con argomenti diversi. Un costruttore privo di argomenti si dice costruttore di default: se non se ne fornisce nessuno, Java si occupa di crearne uno di default automaticamente che si occupa di inizializzare gli attributi. Il costruttore ha lo stesso nome della classe e non ha alcun tipo di ritorno. Inoltre esso richiamato soltanto una volta, cio quando loggetto viene creato e non pu essere pi richimato durante la vita delloggetto.

3 CLASSI E OGGETTI

34

3.5

La classe Persona e loggetto eugenio

Vediamo allora come scrivere una versione migliore della classe Persona, in cui forniamo un costruttore ed un main:
public class Persona{ // Costruttore: inizializza gli attributi nome, cognome, anni public Persona(String nome,String cognome,int anni) { this.nome = nome; this.cognome = cognome; this.anni = anni; } // questo metodo stampa gli attributi public void stampaDati() { System.out.println("Nome: "+nome); System.out.println("Cognome: "+cognome); System.out.println("Et: "+anni); } // attributi private String nome; private String cognome; private int anni;

// main public static void main(String args[]) { Persona eugenio = new Persona("Eugenio","Polito",26); eugenio.stampaDati(); } }

Commentiamo questa classe: abbiamo denito il costruttore Persona(String nome, String cognome, int anni) che si occupa di ricevere in ingresso i tre parametri nome, cognome ed anni e si occupa di inizializzare gli attributi nome, cognome e anni con i valori dei parametri. Come si pu notare stata usata

3 CLASSI E OGGETTI

35

la parola chiave this: questo non altro che un puntatore che fa riferimento alloggetto attuale (o implicito). Quindi la sintassi this.nome signica fai riferimento allattributo nome delloggetto corrente. In questo caso essenziale perch il nome dellargomento ed il nome dellattributo sono identici. Come vedremo, this utile anche per richiamare altri costruttori. Il metodo stampaDati() serve per stampare gli attributi delloggetto. Il metodo main(...) contiene al suo interno due istruzioni:
Persona eugenio = new Persona("Eugenio","Polito",26); con tale istruzione stiamo creando loggetto eugenio: esso viene costruito con il costruttore che ha la rma (gli argomenti) String,String,int (lunico che abbiamo denito). A run time la situazione, dopo questo assegnamento, sar la seguente:
eugenio

Persona
nome = "Eugenio" cognome = "Polito" anni = 26

stampaDati()

Figura 17: Loggetto eugenio dopo la costruzione


eugenio.stampaDati(); richiama il metodo stampaDati() sulloggetto eugenio; il . viene usato per accedere al metodo.

E se avessimo voluto costruire loggetto col costruttore di default ? Avremmo ottenuto un errore, perch nella classe non sarebbe stato trovato dal compilatore alcun costruttore senza argomenti, quindi bene fornirne uno:
public class Persona{ // Costruttore di default public Persona() { this("","",0); } // Costruttore: inzializza gli attributi nome, cognome, anni public Persona(String nome,String cognome,int anni) { this.nome = nome;

3 CLASSI E OGGETTI
this.cognome = cognome; this.anni = anni; } // questo metodo stampa gli attributi public void stampaDati() { System.out.println("Nome: "+nome); System.out.println("Cognome: "+cognome); System.out.println("Et: "+anni); } // attributi private String nome; private String cognome; private int anni;

36

// main public static void main(String args[]) { Persona eugenio = new Persona("Eugenio","Polito",26); Persona anonimo = new Persona(); eugenio.stampaDati(); anonimo.stampaDati(); } }

Il costruttore di default richiama il costruttore che ha come argomenti: String, String, int, attraverso il riferimento allargomento implicito this. In questo esempio il costruttore un metodo che usa loverloading: in base al numero e tipo di argomenti, il compilatore seleziona la versione corretta del metodo (cfr. Sezione 2.7). Loutput del programma il seguente:
Nome: Eugenio Cognome: Polito Et: 26 Nome: Cognome: Et: 0

3 CLASSI E OGGETTI

37

3.6
3.6.1

Realizzare le relazioni fra classi


Uso

Riprendiamo lesempio della sezione 2.6.1: vediamo come si realizza la relazione di uso. Supponiamo che la classe Persona usi la classe Computer per eseguire il prodotto e la somma di 2 numeri, quindi deniamo la classe Computer e poi la classe Persona:
public class Computer { // restituisce il prodotto di a * b public int calcolaProdotto(int a, int b) { return a*b; } // restituisce la somma di a + b public int calcolaSomma(int a, int b) { return a+b; } }

Tale classe ha il metodo calcolaProdotto(...) che si occupa di calcolare il prodotto di due numeri, passati come argomento e di restituirne il risultato (return a*b;). Il discorso analogo per il metodo calcolaSomma(...) . La classe Persona invece :
public class Persona { // Costruttore di default public Persona() { this("","",0); } // Costruttore: inizializza gli attributi nome, cognome, anni public Persona(String nome,String cognome,int anni) { this.nome = nome; this.cognome = cognome;

3 CLASSI E OGGETTI
this.anni = anni; } // questo metodo stampa gli attributi public void stampaDati() { System.out.println("Nome: "+nome); System.out.println("Cognome: "+cognome); System.out.println("Et: "+anni); } /* usa loggetto phobos istanza di Computer per eseguire il prodotto e la somma degli interi a e b passati come argomenti */ public void usaComputer(int a, int b) { Computer phobos = new Computer(); int res = phobos.calcolaProdotto(a,b); System.out.println("Risultato del prodotto "+a+ " * "+b+": "+res); res = phobos.calcolaSomma(a,b); System.out.println("Risultato della somma "+a+ " + "+b+": "+res); } // attributi private String nome; private String cognome; private int anni;

38

// main public static void main(String args[]) { Persona eugenio = new Persona("Eugenio","Polito",26); eugenio.usaComputer(5,5); } }

Il metodo usaComputer(...) crea (quindi usa) un oggetto phobos, istanza della classe Computer (Computer phobos = new Computer();) richiama il metodo

3 CLASSI E OGGETTI

39

calcolaProdotto(...) su phobos, passandogli gli argomenti a e b. Il risultato del calcolo viene posto temporaneamente nella variabile locale res: alluscita dal metodo tale variabile verr eliminata; ogni variabile locale deve essere inizializzata, altrimenti il compilatore riporta un errore! L istruzione successiva System.out.println(...) stampa loutput a video. res = phobos.calcolaSomma(a,b); richiama sulloggetto phobos il metodo calcolaSomma(...) ed il risultato viene posto in res (tale variabile stata gi dichiarata quindi non si deve specicare di nuovo il tipo, inoltre il risultato del prodotto viene perso perch adesso res contiene il valore della somma!). Listruzione successiva stampa il risultato della somma. Notiamo che cos come la variabile locale res nasce, vive e muore in questo metodo, anche loggetto phobos ha lo stesso ciclo di vita: quando il metodo termina, loggetto phobos viene distrutto automaticamente dal Garbage Collector della JVM e la memoria da lui occupata viene liberata. N.B. Gli oggetti costruiti nel main (cos come le variabili) vivono per tutta la durata del programma! Nel main viene creato loggetto eugenio che invoca il metodo usaComputer(...) per usare il computer.

3.6.2

Metodi static: un esempio

Riprendiamo la classe Computer: come possiamo notare, non ha degli attributi; in realt, non ci importa istanziare tale classe perch, cos come stata denita, funge pi da contenitore di metodi che da classe istanziabile. Pertanto i metodi di tale classe li possiamo denire static:
public class Computer{ // restituisce il prodotto di a * b public static int calcolaProdotto(int a, int b) { return a*b; } // restituisce la somma di a + b public static int calcolaSomma(int a, int b) { return a+b; } }

Adesso dobbiamo rivedere il metodo usaComputer(...) della classe Persona:

3 CLASSI E OGGETTI
public class Persona { // Costruttore di default public Persona() { this("","",0); }

40

// Costruttore: inizializza gli attributi nome, cognome, anni public Persona(String nome,String cognome,int anni) { this.nome = nome; this.cognome = cognome; this.anni = anni; } // questo metodo stampa gli attributi public void stampaDati() { System.out.println("Nome: "+nome); System.out.println("Cognome: "+cognome); System.out.println("Et: "+anni);

/* usa i metodi della classe Computer per eseguire il prodotto e la somma fra gli interi a e b passati come argomenti */ public void usaComputer(int a, int b) { int res = Computer.calcolaProdotto(a,b); System.out.println("Risultato del prodotto "+a+ " * "+b+": "+res); res = Computer.calcolaSomma(a,b); System.out.println("Risultato della somma "+a+ " + "+b+": "+res); } // attributi private String nome; private String cognome; private int anni;

3 CLASSI E OGGETTI
// main public static void main(String args[]) { Persona eugenio = new Persona("Eugenio","Polito",26); eugenio.usaComputer(5,5); } }

41

Come si pu notare nel metodo usaComputer(...) , questa volta non viene creato un oggetto istanza della classe Computer, ma si usa questultima per accedere ai metodi calcolaSomma(...) e calcolaProdotto(...) , essendo dei metodi static. 3.6.3 Aggregazione

Riprendiamo lesempio discusso nella sezione 2.6.2: si diceva che la classe Persona aggrega la classe Data, perch ogni persona ha una data di nascita. Deniamo la classe Data:
public class Data { /* Costruttore: inizializza gli attributi giorno, mese, anno con i valori passati come argomenti */ public Data(int giorno, int mese, int anno) { this.giorno = giorno; this.mese = mese; this.anno = anno; } // stampa la Data public void stampaData() { System.out.println(giorno+"/"+mese+"/"+anno); } // attributi private int giorno, mese, anno; }

Tale classe ha gli attributi giorno, mese e anno che vengono inizializzati col costruttore e possono essere stampati a video col metodo stampaData(). La classe Persona:

3 CLASSI E OGGETTI
public class Persona { /* Costruttore: inizializza gli attributi nome, cognome, e data di nascita */ public Persona(String nome, String cognome, int giorno, int mese, int anno) { this.nome = nome; this.cognome = cognome; dataDiNascita = new Data(giorno,mese,anno); } /* stampa gli attributi e richiama il metodo stampaData() sulloggetto dataDiNascita per la stampa della data di nascita */ public void stampaDati() { System.out.println("Nome: "+nome); System.out.println("Cognome: "+cognome); System.out.print("Nato il: "); dataDiNascita.stampaData(); } // attributi private String nome; private String cognome; private Data dataDiNascita;

42

// main public static void main(String args[]) { Persona eugenio = new Persona("Eugenio","Polito",31,12,1976); eugenio.stampaDati(); } }

contiene gli attributi nome, cognome e dataDiNascita (istanza di Data): quindi laggregazione si realizza in Java come attributo. Notiamo che loggetto dataDiNascita viene creato nel costruttore con gli argo-

3 CLASSI E OGGETTI

43

menti passati come parametri: loggetto viene costruito solo quando si sa come farlo. Osserviamo che lincapsulamento garantito: gli attributi delloggetto dataDiNascita possono essere letti solo col metodo stampaData(). N.B. Come si detto il main deve comparire una sola volta in una sola classe; per chiarezza, quando si ha pi di una classe, consigliabile porlo in unaltra classe. Quindi, in questo caso, lo togliamo dalla classe Persona e lo poniamo in una nuova classe, diciamo Applicazione:
public class Persona { /* Costruttore: inizializza gli attributi nome, cognome, e data di nascita */ public Persona(String nome, String cognome, int giorno, int mese, int anno) { this.nome = nome; this.cognome = cognome; dataDiNascita = new Data(giorno,mese,anno); } /* stampa gli attributi e richiama il metodo stampaData() sulloggetto dataDiNascita per la stampa della data di nascita */ public void stampaDati() { System.out.println("Nome: "+nome); System.out.println("Cognome: "+cognome); System.out.print("Nato il: "); dataDiNascita.stampaData(); } // attributi private String nome; private String cognome; private Data dataDiNascita; }

e la classe Applicazione conterr il main:


public class Applicazione {

3 CLASSI E OGGETTI

44

// main public static void main(String args[]) { Persona eugenio = new Persona("Eugenio","Polito",31,12,1976); eugenio.stampaDati(); } }

In seguito verr utilizzato questo modo di procedere. 3.6.4 Ereditariet

Vogliamo estendere la classe Persona in modo da gestire la classe Studente, cio vogliamo che Studente erediti da Persona: questo logicamente vero dal momento che ogni Studente una Persona. Deniamo la classe Persona:
import java.util.Random; public class Persona { /* Costruttore: inizializza gli attributi nome, cognome, e data di nascita */ public Persona(String nome, String cognome, int giorno, int mese, int anno) { this.nome = nome; this.cognome = cognome; dataDiNascita = new Data(giorno,mese,anno); } /* stampa gli attributi e richiama il metodo stampaData() sulloggetto dataDiNascita per la stampa della data di nascita */ public void stampaDati() { System.out.println("Nome: "+nome); System.out.println("Cognome: "+cognome); System.out.print("Nato il: ");

3 CLASSI E OGGETTI
dataDiNascita.stampaData(); }

45

// autoesplicativo public void mangia() { System.out.println("\nMangio con forchetta e coltello\n"); } // stampa casualmente n numeri (magari da giocare al lotto:) public void conta(int n) { System.out.print("Conto: "); Random r = new Random(); for (int i = 0; i < n; i++) System.out.print(r.nextInt(n)+"\t"); System.out.println(); } // attributi private String nome; private String cognome; private Data dataDiNascita; }

Con listruzione import java.util.Random si sta importando la classe Random che contenuta nel package java.util (per luso dei package vedere la sezione 5) e serve per generare dei numeri pseudo-casuali. Il costruttore ed il metodo stampaDati() sono stati gi discussi. Il metodo mangia() stampa a video un messaggio molto eloquente; il simbolo n serve per andare a capo. Il metodo conta(...) riceve come argomento un intero n, e stampa n numeri casuali attraverso un ciclo iterativo (per i cicli vedere 5). Adesso vogliamo denire la classe Studente come sottoclasse di Persona, in cui:

il metodo mangia() viene ereditato; il metodo conta(...) viene riscritto; il metodo stampaDati() viene esteso. viene aggiunto il metodo faiFilaInSegreteria() ;

3 CLASSI E OGGETTI
Supponiamo inoltre che la nuova classe abbia lattributo anniDiScuola. La classe Studente dunque:

46

public class Studente extends Persona { /* Costruttore: richiama il costruttore della classe base (inializzando gli attributi nome, cognome, dataDiNascita) ed inializza il membro anniDiScuola */ public Studente(String nome, String cognome, int giorno, int mese, int anno, int anniDiScuola) { super(nome,cognome,giorno,mese,anno); this.anniDiScuola = anniDiScuola; } /* Riscrive il metodo omonimo della classe base: Stampa i numeri 1,2,...,n */ public void conta(int n) { System.out.print("Conto: "); for (int i = 1; i <= n; i++) System.out.print(i+"\t"); System.out.println(); } /* Estende il metodo omonimo della classe base: richiama il metodo della classe base omonimo ed in pi stampa lattributo anniDiScuola */ public void stampaDati() { super.stampaDati(); System.out.println("Anni di scuola: "+anniDiScuola); } /* stampa il messaggio ... */ public void faiFilaInSegreteria() {

3 CLASSI E OGGETTI
System.out.println("...Aspetto il mio turno in segreteria..."); } // attributo private int anniDiScuola; }

47

In Java lereditariet resa possibile con la parola chiave extends. Il costruttore richiama il costruttore della classe base, che ha la rma String, String, int ,int, int, attraverso la parola chiave super; inoltre inizializza il membro anniDiScuola. Notiamo che per costruire gli attributi nome, cognome, dataDiNascita necessario ricorrere al costruttore della classe base perch hanno tutti campo di visibilit (o scope) private. Il metodo conta(...) stato riscritto: ora stampa correttamente i numeri 1,2,. . . ,n. Il metodo stampaDati(...) stato esteso: richiama il metodo omonimo della classe base ed in pi stampa lattributo anniDiScuola. Inne stato aggiunto il metodo faiFilaInSegreteria che stampa un messaggio di attesa. . . Come si detto gli attributi della classe base non sono accessibili alla classe derivata perch hanno scope private. Tuttavia possibile consentire solo alle classi derivate di avere un accesso protetto agli attributi, attraverso il modicatore di accesso protected. La classe Persona pu essere pertanto riscritta nel seguente modo:
import java.util.Random; public class Persona{ // Costruttore di default public Persona() { this("","",0,0,0); } /* Costruttore: inizializza gli attributi nome, cognome, anni e data di nascita */ public Persona(String nome, String cognome, int giorno, int mese, int anno)

3 CLASSI E OGGETTI
{ this.nome = nome; this.cognome = cognome; this.dataDiNascita = new Data(giorno,mese,anno); } /* stampa gli attributi e richiama il metodo stampaData() sulloggetto dataDiNascita per la stampa della data di nascita */ public void stampaDati() { System.out.println("Nome: "+nome); System.out.println("Cognome: "+cognome); System.out.print("Nato il: "); dataDiNascita.stampaData(); }

48

// autoesplicativo public void mangia() { System.out.println("\nMangio con forchetta e coltello\n"); } // stampa casualmente n numeri (magari da giocare al lotto:) public void conta(int n) { System.out.print("Conto: "); Random r = new Random(); for (int i = 0; i < n; i++) System.out.print(r.nextInt(n)+"\t"); System.out.println(); }

// attributi protected String nome; protected String cognome; protected Data dataDiNascita; // main public static void main(String args[])

3 CLASSI E OGGETTI
{

49

Persona eugenio = new Persona("Eugenio","Polito",31,12,1976); eugenio.stampaDati(); eugenio.mangia(); eugenio.conta(5); } }

Adesso possiamo avere accesso diretto agli attributi della classe base dalla classe derivata Studente:
public class Studente extends Persona { /* Costruttore: inizializza gli attributi public Studente(String nome, String cognome, int giorno, int mese, int anno, int anniDiScuola) { this.nome = nome; this.cognome = cognome; this.dataDiNascita = new Data(giorno,mese,anno); this.anniDiScuola = anniDiScuola; } /* Riscrive il metodo omonmio della classe base: Stampa i numeri 1,2,...,n */ public void conta(int n) { System.out.print("Conto: "); for (int i = 1; i <= n; i++) System.out.print(i+"\t"); System.out.println(); } /* Estende il metodo omonimo della classe base: richiama il metodo della classe base omonimo ed in pi stampa lattributo anniDiScuola */ public void stampaDati()

3 CLASSI E OGGETTI
{ super.stampaDati(); System.out.println("Anni di scuola: "+anniDiScuola); }

50

/* stampa il messaggio ... */ public void faiFilaInSegreteria() { System.out.println("...Aspetto il mio turno in segreteria..."); } // attributo private int anniDiScuola; }

Notare come adesso nel costruttore si possa accedere direttamente agli attributi della classe base (this.nome, this.cognome, this.dataDiNascita ). Unaltra cosa da osservare che si reso necessario inserire un costruttore di default nella classe base perch il costruttore della classe derivata va a cercare subito il costruttore di default della superclasse e poi inizializza gli attributi! Vediamo una applicazione di esempio:
public class Applicazione { public static void main(String args[]) { Persona bill = new Persona("Bill","Cancelli",13,13,1984); bill.stampaDati(); bill.conta(5); bill.mangia(); Studente tizio = new Studente("Pinco","Pallino",1,1,1970,15); tizio.stampaDati(); tizio.conta(5); tizio.mangia(); tizio.faiFilaInSegreteria(); } }

Una possibile esecuzione la seguente: Nel main abbiamo creato loggetto bill (istruzione Persona bill = new Persona("Bill","Cancelli",13,13,1984);), il quale invoca i metodi stampaDati();, conta(5); e mangia();.

3 CLASSI E OGGETTI

51

Figura 18: Esecuzione del programma Applicazione.java stato quindi creato loggetto tizio come istanza della classe Studente. Richiama il metodo stampaDati: come si pu vedere in Figura 18, oltre al nome, cognome e data di nascita viene stampato lattributo anniDiScuola (ricordiamoci che tale metodo stato esteso, proprio per permettere di stampare tale attributo). Viene poi richiamato il metodo conta(5): siccome tale metodo stato riscritto, stampa a video la sequenza corretta dei numeri 1,2,. . . ,n. Loggetto tizio invoca poi il metodo mangia(), che essendo ereditato stampa lo stesso messaggio che stato stampato precedentemente dallo stesso metodo invocato da bill. Inne tizio invoca il metodo aggiunto nella classe Studente faiFilaInSegreteria() che stampa un messaggio. Osserviamo che se avessimo invocato faiLaFilaInSegreteria() sulloggetto bill avremmo ottenuto un messaggio di errore, perch tale metodo non denito nella classe Persona.

3 CLASSI E OGGETTI

52

3.7

Classi astratte

Nella sezione 2.6.4 abbiamo parlato del concetto di classe astratta: vediamo adesso come si realizza in Java. Come si detto, un Animale pu essere considerato un contenitore di operazioni (dal momento che non si sa come denire tali operazioni in generale: come mangia() o comunica() un Animale?) per tutte le classi derivate, come Persona, Cane etc.: cio la classe Animale una classe astratta. Supponiamo che tale classe abbia due metodi astratti mangia() e comunica() ed uno concreto dorme(), dal momento che tutti gli animali dormono allo stesso modo:
public abstract class Animale { // metodo astratto per mangiare public abstract void mangia(); // metodo astratto per comunicare public abstract void comunica(); // metodo concreto per dormire public void dorme() { System.out.println("Dormo..."); } }

Quindi una classe astratta denita tale con la keyword abstract: questo tipo di classe pu contenere sia metodi astratti (deniti ovviamente abstract), sia metodi concreti. Ogni classe derivata da una classe astratta deve implementare i metodi astratti della classe base! Per esempio una eventuale classe Cane potrebbe avere questa forma:
public class Cane extends Animale { // costruttore public Cane(String nome) { this.nome = nome; }

3 CLASSI E OGGETTI
// implementa il metodo della classe base public void comunica() { System.out.println("Sono "+nome+" e faccio Bau Bau"); } // implementa il metodo della classe base public void mangia() { System.out.println("Mangio con la bocca e le zampe"); } // attributo private String nome; }

53

Pertanto Cane estende la classe Animale: realizza i metodi astratti mangia() e comunica ed eredita il metodo dorme(). Analogamente una classe Persona potrebbe essere cos:
public class Persona extends Animale { // costruttore public Persona(String nome, String cognome) { this.nome = nome; this.cognome = cognome; } // implementa il metodo della classe base public void comunica() { System.out.println("...Salve mondo, sono "+nome+" "+cognome); } // implementa il metodo della classe base public void mangia() { System.out.println("Mangio con forchetta e coltello"); } // estende il metodo della classe base

3 CLASSI E OGGETTI
public void dorme() { super.dorme(); System.out.println("ed in pi russo!"); } // aggiunge il metodo: public void faiGuerra() { System.out.println("...Sono un animale intelligente perch faccio le guerre..."); } private String nome,cognome; }

54

Questa classe implementa i metodi astratti della classe base, estende il metodo dorme() ed aggiunge il metodo faiGuerra(). Deniamo un main nella classe Applicazione:
public class Applicazione { public static void main(String args[]) { Cane bill = new Cane("bill"); bill.comunica(); bill.mangia(); bill.dorme(); System.out.println("\n-----------------------"); Persona george = new Persona("George","Fluff"); george.comunica(); george.mangia(); george.dorme(); george.faiGuerra(); } }

3 CLASSI E OGGETTI
Lesecuzione del programma la seguente:

55

Figura 19: Esecuzione del programma Applicazione.java Nel main viene creato loggetto bill che una istanza della classe Cane: su di esso viene invocato il metodo comunica() che stampa lattributo nome (inizializzato nel costruttore) ed il verso Bau Bau. bill invoca quindi il metodo mangia(), che stampa una stringa che ci spiega come il cane riesca a mangiare. Inne viene richiamato su bill il metodo dorme(). Allo stesso modo viene creato loggetto george che unistanza di Persona: esso invoca gli stessi metodi che invoca loggetto bill ed in pi richiama il metodo faiGuerra().

3 CLASSI E OGGETTI

56

3.8

Interfacce

Le interfacce sono un meccanismo proprio di Java che, come vedremo nella sezione successiva, consentono di avere un supporto parziale ma sufciente per lereditariet multipla. Attraverso una interfaccia si denisce un comportamento comune a classi che fra di loro non sono in relazione. Come si detto, anche una classe astratta denisce un contenitore di metodi per le classi derivate, quindi in questo caso si usa la relazione di ereditariet. Quando si parla invece di interfaccia si deniscono i metodi che le classi dovranno implementare, pertanto in una interfaccia non possono esistere metodi concreti! In UML una interfaccia si disegna cos:

Figura 20: Diagramma UML per interface Considerimo, per esempio, i le HTML ed i le bytecode di Java: ovviamente essi non hanno nulla in comune, se non il fatto di supportare le stesse operazioni, come apri(...), chiudi(...), etc. Vediamo allora come costruire una interfaccia comune di operazioni da usare su diversi le, per aprirli, determinarne il tipo e chiuderli. Deniamo allora un interfaccia FileType:
public interface FileType { // apre il file public void open(); // verifica se il tipo di file OK public boolean fileTypeOk(); // chiude il file public void close(); }

Si sta dicendo che linterfaccia FileType (denita con la keyword interface) denisce i tre metodi open(), fileTypeOk(), close() ed ogni classe che vuole implementare questa interfaccia deve implementare i metodi in essa deniti. Supponiamo adesso di voler aprire e vericare un le bytecode di Java (per la struttura dei bytecode Java cfr. [1]):

3 CLASSI E OGGETTI
import java.io.*; public class FileClass implements FileType { /* Costruttore: inizializza il nome del file che si vuole leggere */ public FileClass(String nome) { nomeDelFile = nome; }

57

// apre il file fisico il cui nome nomeDelFile public void open() { try { fileClass = new DataInputStream(new FileInputStream(nomeDelFile)); } catch (FileNotFoundException fne) { System.out.println("File "+nomeDelFile+" non trovato."); } } /* verifica se il file un file bytecode di Java: legge i primi 4 byte (cio 32 bit = int) e controlla se tale intero il numero esadecimale 0xcafebabe, cio lheader del file .class */ public boolean fileTypeOk() { int cafe = 0; try { cafe = fileClass.readInt(); } catch (IOException ioe) {} if ((cafe ^ 0xCAFEBABE) == 0) return true; else return false; } // chiude il file fisico public void close() { try {

3 CLASSI E OGGETTI
fileClass.close(); } catch (IOException ioe) { System.out.println("Non posso chiudere il file"); } } // file fisico private DataInputStream fileClass; // nome del file private String nomeDelFile;

58

// main public static void main(String args[]) { if (args.length != 0) { FileClass myFile = new FileClass(args[0]); myFile.open(); if (myFile.fileTypeOk()) System.out.println("Il file "+args[0]+ " un bytecode Java"); else System.out.println("Il file "+args[0]+ " non un file .class!"); myFile.close(); } else System.out.println("uso: java FileClass \"nome del file\""); } }

Provare a compilarlo e ad eseguirlo (sintassi: java FileClass nome dove nome un nome di le .class, per esempio provare con: java FileClass FileClass.class. . . ) La classe FileClass implementa le operazioni dellinterfaccia attraverso la keyword implements. Quindi, come si vede dal codice, la classe deve implementare tutti i metodi dellinterfaccia. Il costruttore riceve come argomento il nome del le che usa per inizializzare lattributo nomeDelFile. Il metodo open() implementa il metodo omonimo dellinterfaccia FileType: quindi tenta di aprire il le come stream di byte e se non trova il le solleva una eccezione (per i le e le eccezioni cfr. la sezione 5).

3 CLASSI E OGGETTI

59

Il metodo fileTypeOk verica se il le in aperto una bytecode Java: se lheader o intestazione comincia con il numero esadecimale 0xCAFEBABE (0x signica che ci che segue un numero in base 16) allora il le un bytecode Java, altrimenti non lo . Notare che per il test si usato loperatore fra bit XOR - in Java , che restituisce 0 se i bit sono uguali, 1 altrimenti. close chiude lo stream: se non lo trova (. . . magari stato cancellato o spostato. . . ) solleva una eccezione. Il main richiama in ordine i tre metodi di cui sopra. Dal momento che un bytecode un le di byte, si sono usate le classi di accesso ai le del package java.io. In 5 verr discusso come accedere ai le. In UML implements si disegna cos:

Figura 21: Diagramma UML per implements Dunque FileClass implementa linterfaccia FileType. Analogamente se volessimo vericare un le HTML, un ELF di Linux (le esegubile) etc., non dobbiamo far altro che scrivere delle classi che implementano le operazioni dellinterfaccia FileType. Lutilizzo delle interfacce conveniente, almeno per due motivi:

si separa linterfaccia dallimplementazione; si ha una garanzia per non fare errori: si modica solo limplementazione e
non linterfaccia.

3 CLASSI E OGGETTI

60

3.9

Ereditariet multipla

In 2.6.5 si parlato della ereditariet multipla in teoria: realizziamo adesso qualche esempio pratico. Come si detto in 2.6.5 lereditariet multipla ha tre forme, di cui solo due sono supportate in Java. Vediamo come realizzare il matrimonio fra una classe concreta ed una astratta: poich in Java ogni classe ha un solo padre, non possibile ereditare da due o pi classi contemporaneamente; sembrerebbe a prima vista che il matrimonio non possa essere celebrato. In realt possibile farlo, perch una interfaccia una classe astratta senza metodi concreti, quindi possiamo fare un matrimonio fra una classe concreta ed una interfaccia. Lesempio della Pila della sezione 2.6.5 diventa pertanto:

Figura 22: Diagramma UML per il matrimonio fra classe concreta ed interfaccia La nostra Pila dovr avere una struttura FIFO (First In First Out) anche detta FCFS (First Come First Served) cio il primo elemento che entra deve essere il primo elemento ad uscire (pensate ad una pila di piatti. . . ); quindi la struttura che dobbiamo implementare questa:
pop() top push(20)

10 20 5 16 4 5

Figura 23: Una Pila di numeri interi Loperazione push(...) inserisce un elemento sulla cima della pila, mentre

3 CLASSI E OGGETTI

61

pop() preleva lelemento in cima. Lattributo top punta alla cima della struttura. Scriviamo allora linterfaccia Stack: essa denisce le operazioni in astratto che verranno implementate da Pila sullarray. public interface Stack { // inserisce un oggetto element nella pila public void push(Object element); // preleva loggetto dalla cima della pila public Object pop(); // verifica se la pila piena public boolean isFull(); // verifica se la pila vuota public boolean isEmpty(); } Pila deve implementare Stack ed estendere array: poich Java fornisce un buon supporto per gli array attraverso la classe Vector del package java.util, useremo tale classe come array: import java.util.Vector; public class Pila extends Vector implements Stack { // alloca una Pila di numElements elementi public Pila(int numElements) { super(numElements); maxElements = numElements; top = 0; } // inserisce un element nella Pila public void push(Object element) { if (!isFull()) { super.addElement(element);

3 CLASSI E OGGETTI
top++; } else System.out.println("Pila Piena!"); } // preleva lelemento in cima alla Pila public Object pop() { if (!isEmpty()) return super.remove(--top); else { System.out.println("Pila Vuota!"); return null; } }

62

// restituisce true se la Pila vuota, false altrimenti public boolean isFull() { return (top == maxElements); } /* riscrive il metodo omonimo della superclasse : restituisce true se la Pila vuota, false altrimenti */ public boolean isEmpty() { return (top == 0); } // puntatore alla cima della Pila private int top; // contatore del numero degli elementi della Pila private int maxElements; }

Poich Pila un Vector, supporta tutti i metodi di tale classe, inoltre poich implementa linterfaccia Stack deve implementare tutti i metodi di tale interfaccia. Il costruttore richiama il costruttore della classe base Vector, setta lattributo numElements (cio il numero massimo di elementi che la pila pu memorizzare) al valore passatogli come argomento e inizializza il top a 0 (quindi la pila vuo-

3 CLASSI E OGGETTI

63

ta). I metodi isFull() ed isEmpty() restituiscono true se, rispettivamente la pila piena (quindi top uguale al massimo valore di elementi che la pila pu supportare) e se la pila vuota (top uguale a 0). Il metodo push(...) inserisce un elemento passatogli come argomento in cima alla pila: se la pila piena viene segnalato un errore. Notiamo che in realt linserimento avviene tramite la chiamata al metodo addElement(...) della classe base Vector che si preoccupa di inserire lelemento nel vettore sico. pop loperazione complementare a push(...). Una applicazione desempio potrebbe essere la seguente:
public class ProvaPila { public static void main(String args[]) { if (args.length != 0) { Pila pila = new Pila((new Integer(args[0])).intValue()); // inserisci elem. finch la pila non piena int i = 1; while (!pila.isFull()) { pila.push(new Integer(i++)); } // preleva elem. finch la pila non vuota while (!pila.isEmpty()) { System.out.println("elemento prelevato: " +pila.pop()); } } else System.out.println("uso: java ProvaPila num_elem"); } }

3 CLASSI E OGGETTI
Una possibile esecuzione la seguente:

64

Figura 24: Esecuzione di ProvaPila Osserviamo adesso un fatto importante: riprendiamo literfaccia Stack; consideriamo i due metodi

public void push(Object element); public Object pop();


Come si pu vedere, push(...) prende come argomento un elemento il cui tipo Object, mentre la funzione pop() restituisce un Object. Cosa vuol dire questo? Semplicemente che tali funzioni operano su oggetti di tipo Object: ma come si detto nella sottosezione 3.6.4, ogni classe deriva da Object, quindi questi metodi funzionano su qualunque tipo! Pertanto la nostra Pila, che implementa linterfaccia Stack, sar una pila che potr contenere elementi di qualsiasi tipo: allora potr contenere numeri interi, numeri reali, oggetti Persona, etc. etc. Utilizzando il tipo cosmico Object (come parametro di funzione e/o tipo di ritorno di un metodo), si pu simulare il polimorsmo parametrico (cfr. sezione 2.7); tuttavia, mentre in C++ (cfr. [2]) possibile istanziare oggetti dello stesso tipo (cio con un template di C++ si possono avere solo collezioni omogenee), in Java possibile mixare tipi diversi (ogni classe -un Object), quindi si possono ottenere collezioni eterogenee. Vediamo cosa signica questo fatto nel nostro caso:

3 CLASSI E OGGETTI

65

public class ProvaPila { public static void main(String args[]) { if (args.length != 0) { Pila pila = new Pila((new Integer(args[0])).intValue()); // inserisci elem. finch la pila non piena int i = 1; while (!pila.isFull()) { // inserisce un intero pila.push(new Integer(i)); // inserisce un reale pila.push(new Float(i*Math.PI)); // inserisce una stringa pila.push(new String("Sono il numero: "+i)); i++; } // preleva elem. finch la pila non vuota while (!pila.isEmpty()) { System.out.println("elemento prelevato: " +pila.pop()); } } else System.out.println("uso: java ProvaPila num_elem"); } }

Stavolta nella pila vengono inseriti rispettivamente: un numero intero, un numero reale ed una stringa: abbiamo cos ottenuto una pila universale di oggetti. Occorre osservare che una classe pu implementare pi interfacce: ad esempio, se vogliamo che la nostra Pila salvi il contenuto della pila su un le, possiamo scrivere Pila cos:
public class Pila extends Vector implements Stack, FileStack { ... }

3 CLASSI E OGGETTI

66

dove Vector e Stack sono le stesse viste sopre, mentre FileStack una interfaccia che denisce i metodi per laccesso ai le sici. Pertanto una classe pu estendere una sola classe base ma pu implementare pi interfacce. La seconda forma di ereditariet multipla il duplice sottotipo: cio una classe implementa due interfacce ltrate. Se abbiamo una interfaccia A ed una interfaccia B, possibile fare questo:
public interface A {...}; public interface B extends A {...};

Una interfaccia pu estendere unaltra interfaccia: di pi pu estendere un numero illimitato di interfacce, cio si pu avere una cosa del genere:
public interface X extends A1 ,A2 ,...,An {...}

dove ogni Ai ! i 1 ! 2 !#"$"$"%! n, sono interfacce! Una classe concreta Y implementer X:


public class Y implements X {...}

Lereditariet multipla sottoforma di composizione di oggetti non supportata (cfr. sezione 2.6.5) per questioni di sicurezza del codice e per non rendere complessa la JVM .

4 LE OPERAZIONI SUGLI OGGETTI

67

4
4.1

Le operazioni sugli oggetti


Copia e clonazione

Supponiamo di avere la seguente classe:


public class A { // costruttore di default: richiama il costruttore A(int num) public A() { this(0); } /* costruttore: setta lattributo num al valore passato come argomento */ public A(int num) { this.num = num; } // assegna un nuovo valore a num public void set(int num) { this.num = num; } // stampa num public void print() { System.out.println(num); } // attributo private int num; // main public static void main(String args[]) { A primo = new A(1453); primo.print(); A secondo = new A();

4 LE OPERAZIONI SUGLI OGGETTI


secondo.print(); secondo = primo; secondo.set(16); primo.print(); secondo.print(); } }

68

Se eseguiamo tale programma, otteniamo il seguente output:


1453 0 16 16

Esaminiamo il main: viene creato loggetto primo che inizializza il membro num a 1453; quando si richiama il metodo print() sulloggetto primo, si ottiene a video 1453. Viene poi creato loggetto secondo che viene costruito col costruttore di default (quindi il membro num 0) e ed richiamato su questo oggetto print() che stampa 0. Si esegue poi lassegnamento secondo = primo. Si richiama poi il metodo set(...) sulloggetto secondo, passando come argomento lintero 16. Quando si esegue listruzione secondo.print(); , viene stampato a video il numero 16. Invocando print su primo, invece di ottenere il numero 1453, otteniamo 16. Che cosa successo? Come si detto nella sezione 3.4, la variabile oggetto un riferimento alloggetto, cio essa serve per accedere alle informazioni delloggetto alla quale si riferisce e non per memorizzarle. Allora con lassegnamento secondo = primo;, non si sta facendo una copia di valori, ma si sta facendo una copia di riferimenti: sia primo che secondo puntano allo stesso oggetto. In sostanza si creato un secondo riferimento alloggetto primo.

4 LE OPERAZIONI SUGLI OGGETTI

69

Quando gli oggetti primo e secondo sono stati costruiti, nello heap si ha una situazione del genere:
primo

num = 1453

secondo

num = 0

Figura 25: Gli oggetti primo e secondo dopo la creazione e dopo lassegnamento secondo = primo; si ha:
primo

num = 1453

secondo

num = 0

Figura 26: Gli oggetti primo e secondo dopo lassegnamento secondo = primo; Pertanto ogni modica sullo stato (attributi) di un oggetto si ripercuote sullo stato dellaltro. Vogliamo evitare questa situazione: cio vogliamo che il riferimento, dopo la copia, rimanga intatto. Per fare questo Java mette a dispozione il metodo clone() nella classe Object: quindi baster invocare tale metodo e verr eseguita una copia totale delloggetto (ricordiamo ancora una volta che ogni oggetto deriva da Object implicitamente). necessario implementare linterfaccia Cloneable (gi denita in Java) per indicare che la clonazione delloggetto possibile: il metodo clone() di Java protected, quindi per poterlo invocare necessario implementare Cloneable. Inoltre poich il tipo di ritorno di questo metodo Object, necessario un cast nel tipo corrente delloggetto:

4 LE OPERAZIONI SUGLI OGGETTI

70

public class A implements Cloneable { // costruttore di default: richiama il costruttore A(int num) public A() { this(0); } /* costruttore: setta lattributo num al valore passato come argomento */ public A(int num) { this.num = num; } /* implementa il metodo dellinterfaccia Cloneable: richiama il metodo clone() di Object */ public Object clone() { try { return super.clone(); } catch (CloneNotSupportedException e) { return null; } } // assegna un nuovo valore a num public void set(int num) { this.num = num; } // stampa num public void print() { System.out.println(num); } // attributo private int num;

4 LE OPERAZIONI SUGLI OGGETTI


// main public static void main(String args[]) { A primo = new A(1453); A secondo = new A(); secondo = (A)primo.clone(); secondo.set(16); primo.print(); secondo.print(); } }

71

Se eseguiamo tale codice, otteniamo il seguente output:


1453 16

La situazione nello heap dopo la clonazione, cio dopo listruzione


secondo = (A)primo.clone();

la seguente:
primo

num = 1453

secondo

num = 1453

Figura 27: Gli oggetti primo e secondo dopo la clonazione I due oggetti hanno adesso vite indipendenti.

4 LE OPERAZIONI SUGLI OGGETTI


Dopo listruzione:
secondo.set(16);

72

La situazione la seguente:
primo

num = 1453

secondo

num = 16

Figura 28: Gli oggetti primo e secondo dopo listruzione secondo.set(16); Occorre notare che se una classe aggrega unaltra classe, necessario denire il metodo clone() anche nella classe aggregata, altrimenti questultima non verrebbe clonata se fosse eseguito un clone() su un oggetto dellaltra classe!

4 LE OPERAZIONI SUGLI OGGETTI

73

4.2

Confronto

Unaltra operazione importante, che pu ricorrere spesso in una applicazione vera, il confronto di oggetti della stessa classe. Java mette a disposizione il metodo equals(...) nella classe Object che confronta due oggetti e restituisce

true se i due oggetti sono identici (cio sono lo stesso riferimento), false altrimenti
Tale metodo non molto utile se, per esempio, vogliamo confrontare due persone: in questo caso necessario confrontare tutti gli attributi e restituire true se sono uguali, false altrimenti. Risulta allora conveniente riscrivere tale metodo:
public class Persona { //Costruttore: inizializza gli attributi public Persona(String nome,String cognome,int anni) { this.nome = nome; this.cognome = cognome; this.anni = anni; } /* riscrive il metodo della classe base Object: testa se due persone sono uguali dal punto di vista degli attributi */ public boolean equals(Object object) { if (object instanceof Persona) return ((this.nome.equals(((Persona)object).nome)) && (this.cognome.equals(((Persona)object).cognome)) && (this.anni == ((Persona)object).anni)); else return false; } // attributi private String nome,cognome; private int anni;

4 LE OPERAZIONI SUGLI OGGETTI


// main public static void main(String args[]) { Persona pippo = new Persona("Pippo","Caio",2); Persona pluto = new Persona("Pippo","Caio",2); if (pippo.equals(pluto)) System.out.println("Sono la stessa persona"); else System.out.println("Sono persone diverse"); } }

74

Il metodo public boolean equals(Object object) riscrive il metodo omonimo di Object: come si pu notare, tale metodo inizia con un confronto e precisamente:
if (object instanceof Persona)

questo controllo molto importante. Infatti, quando nel main viene eseguita listruzione pippo.equals(pluto) , al metodo equals(...) si sta passando come argomento loggetto pluto che una istanza di Persona: possiamo immaginare una situazione del genere:
pluto

Persona
nome = "Pippo" cognome = "Caio" anni = 2

Object

Figura 29: Un oggetto ha sempre un riferimento implicito ad Object Poich ogni classe deriva da Object, allora ogni oggetto anche un riferimento ad un Object (a tempo di esecuzione del programma), quindi pluto un oggetto di tipo Persona, ma anche di tipo Object!

4 LE OPERAZIONI SUGLI OGGETTI

75

Se avessimo una classe Cane (con un unico attributo nome) ed un oggetto bill, istanza di tale classe, avremmo allora:
bill

Cane
nome = "bill"

Object

Figura 30: Loggetto bill istanza di Cane Riguardiamo adesso il metodo equals(...) e notiamo che accetta come argomento un Object: nessuno ci vieta allora di scrivere nel main listruzione:
pluto.equals(bill);

Ma ha senso confrontare un oggetto di tipo Persona con un oggetto di tipo Cane? Ovviamente no! Abbiamo quindi bisogno di controllare a run time il tipo dinamico (cio il tipo delloggetto durante lesecuzione del programma che pu essere una istanza di una qualunque classe della gerarchia di ereditariet) delloggetto passato come argomento al metodo equals(...): se il tipo di tale oggetto Persona, allora possiamo confrontare gli attributi dei due oggetti che sono sicuramente istanze di Persona, altrimenti il confronto degli attributi non ha senso (sono ovviamente due oggetti di tipo diverso). Questo controllo viene fatto con la keyword instanceof, la cui sintassi :
if (nome_dell_oggetto instanceof Nome_della_Classe) {...}

il risultato sar true se il tipo dinamico di nome_dell_oggetto esattamente Nome_della_Classe , false altrimenti. Nel nostro caso (sia pippo che pluto sono oggetti Persona), tale controllo (if (object instanceof Persona)) sar true, perch il tipo dinamico di pluto Persona. Osserviamo per che prima e dopo questo controllo stiamo usando pippo come istanza di Object e non di Persona! Pertanto se tentiamo di accedere ad un qualsiasi attributo di Persona, otteniamo un errore a tempo di compilazione. Quindi necessario specicare che se il controllo di instanceof andr a buon ne durante lesecuzione del programma, il tipo di pluto dovr essere ripristinato a Persona: questa operazione detta downcasting e viene eseguita con la sintassi:

4 LE OPERAZIONI SUGLI OGGETTI


(Nome_della_classe_derivata)nome_dell_oggetto

76

Questo tipo di conversione pericolosa: usarla solo quando necessario e soprattutto, prima di eseguire il cast vericare il tipo dinamico delloggetto con instanceof. Come vedremo, questa conversione pu essere spesso evitata se si ricorre al polimorsmo!

4.3

Binding dinamico

Consideriamo due classi, per esempio Base da cui deriva Derivata:


public class Base { // richiama stampa() public void f() { stampa(); } // stampa un messaggio public void stampa() { System.out.println("Sono la classe base"); } } public class Derivata extends Base { // riscrive il metodo della classe base public void stampa() { System.out.println("Sono la classe derivata"); } // stampa la stringa "ciao" public void g() { System.out.println("Ciao dalla classe derivata"); } }

Supponiamo inoltre di avere il seguente main:

4 LE OPERAZIONI SUGLI OGGETTI


public class Prova { public static void main(String args[]) { Base base = new Base(); Derivata derivata = new Derivata(); base.f(); derivata.f(); derivata.g(); } }

77

Se eseguiamo il Prova, otteniamo il seguente output:


Sono la classe base Sono la classe derivata Ciao dalla classe derivata

Vengono creati i due oggetti base e derivata, istanze, rispettivamente, di Base e Derivata. base invoca il metodo f(): esso richiama il metodo stampa() che stampa il messaggio: Sono la classe base. derivata richiama il metodo f() che stato ereditato da Base: a sua volta f() invoca il metodo stampa() (riscritto nella classe derivata). Grazie al meccanismo del binding dinamico, la versione di stampa richiamata su derivata proprio quella che appartiene alla classe Derivata. In sostanza il metodo da selezionare a run-time dipende dal tipo dinamico delloggetto: siccome il tipo dinamico di derivata Derivata, viene selezionato il metodo stampa() che stampa il messaggio Sono la classe derivata. Listruzione derivata.g() stampa a video il messaggio Ciao dalla classe derivata. Adesso modichiamo il main nel seguente modo:
public class Prova { public static void main(String args[]) { Base base = new Base(); Derivata derivata = new Derivata(); base = derivata; base.f();

4 LE OPERAZIONI SUGLI OGGETTI


derivata.f(); } }

78

Stavolta vengono creati i due oggetti base e derivata, e viene eseguito lassegnamento:
base = derivata;

cio viene creato un secondo reference a derivata:


base

Base
f() stampa()

metodo ereditato

derivata

Derivata
f()

stampa() g()

Figura 31: base = derivata; quindi non ci meravigliamo se otteniamo, come risultato, il seguente output:
Sono la classe derivata Sono la classe derivata

del tutto normale perch abbiamo creato un nuovo reference alla classe derivata, il cui tipo dinamico Derivata che contiene il metodo stampa() che stampa il messaggio Sono la classe derivata. Adesso proviamo ad eseguire una nuova istruzione nel main:
base.g();

Se proviamo a compilare, stavolta otteniamo un bel messaggio di errore, che ci informa che g() non un metodo di Base! E anche questo fatto del tutto normale: quando il compilatore avvia la compilazione, si rende conto che il tipo statico di base Base, pertanto quando trova listruzione base.g(), cerca il metodo g() e non lo trova nella classe Base. N.B. Il binding dinamico in Java offerto per default : se lo si vuole disabilitare, necessario dichiarare i metodi final. Tale keyword pu essere applicata anche alle classi: per ci impedisce la derivazione, quindi bene riettere prima di usarla!

4 LE OPERAZIONI SUGLI OGGETTI

79

4.4

Serializzazione

Una operazione interessante ed importante che pu essere fatta su un oggetto la serializzazione: cio la possibilit di salvare lo stato (cio linsieme dei valori degli attributi in un certo istante di tempo) di un oggetto su un le. Successivamente il contenuto delloggetto pu essere ripristinato. Per realizzare questo meccanismo su una classe, questultima deve implementare linterfaccia Serializable del package java.io: tale interfaccia contiene i metodi
void writeObject(java.io.ObjectOutputStream out) throws IOException void readObject(java.io.ObjectInputStrem in) throws IOException, ClassNotFoundExcpetion

che servono, rispettivamente, per salvare ed aprire un oggetto e devono, ovviamente, essere implementate nella classe che realizza la serializzazione. Facciamo un esempio: supponiamo di voler serializzare la classe Persona:
import java.io.Serializable; public class Persona implements Serializable{ // Costruttore di default public Persona() { this("","",0,0,0); } /* Costruttore: inizializza gli attributi nome, cognome, anni e data di nascita */ public Persona(String nome, String cognome, int giorno, int mese, int anno) { this.nome = nome; this.cognome = cognome; this.dataDiNascita = new Data(giorno,mese,anno); } /* stampa gli attributi e richiama il metodo stampaData() sulloggetto dataDiNascita

4 LE OPERAZIONI SUGLI OGGETTI


per la stampa della data di nascita */ public void stampaDati() { System.out.println("Nome: "+nome); System.out.println("Cognome: "+cognome); System.out.print("Nato il: "); dataDiNascita.stampaData(); } // attributi protected String nome; protected String cognome; protected Data dataDiNascita; }

80

Allo stesso modo, la classe Data deve essere serializzata e quindi deve implementare linterfaccia Serializable:
import java.io.Serializable; public class Data implements Serializable { /* Costruttore: inizializza gli attributi giorno, mese, anno con i valori passati come argomenti */ public Data(int giorno, int mese, int anno) { this.giorno = giorno; this.mese = mese; this.anno = anno; } // stampa la Data public void stampaData() { System.out.println(giorno+"/"+mese+"/"+anno); } // attributi private int giorno, mese, anno; }

4 LE OPERAZIONI SUGLI OGGETTI

81

A questo punto possiamo denire i due metodi che realizzano il salvataggio ed il ripristino degli oggetti:
import java.io.*; public class Serial { // Scrive loggetto sul file "persona.dat" public void writeFile() { Persona pippo = new Persona("Tizio","Caio",1,1,2003); try{ FileOutputStream file = new FileOutputStream(fileName); ObjectOutputStream obj = new ObjectOutputStream(file); obj.writeObject(pippo); obj.close(); } catch (IOException ioe) {System.out.println(ioe);} } // legge loggetto dal file "persona.dat" e lo stampa a video public void readFile() { Persona pippo = null; try { FileInputStream file = new FileInputStream(fileName); ObjectInputStream obj = new ObjectInputStream(file); pippo = (Persona)obj.readObject(); obj.close(); pippo.stampaDati(); } catch (Exception e) {System.out.println(e);} } // attributo: nome del file in cui memorizzare gli oggetti private String fileName = "persona.dat"; public static void main(String args[]) { Serial mySerialization = new Serial(); mySerialization.writeFile(); mySerialization.readFile(); } }

4 LE OPERAZIONI SUGLI OGGETTI

82

Il metodo writeFile() crea un oggetto pippo di tipo Persona, e prova ad aprire il le persona.dat: se la classe Persona, cos come Data, non avessero implementato linterfaccia Serializable, il blocco try {...} catch {...} avrebbe sollevato una eccezione a run time. Viene quindi aperto uno stream di output (vedere la sezione 5) di nome persona.dat, su cui vogliamo inviare le informazioni delloggetto (istruzione ObjectOutputStream obj = new ObjectOutputStream(file); ). La serializzazione delloggetto viene eseguita con listruzione obj.writeObject(pippo); il le viene quindi chiuso. Simmetricamente, readFile() apre il le persona.dat come stream di input e legge loggetto: notare il cast forzato in Persona, poich il tipo restituito da readObject() Object. Sulloggetto appena letto viene invocato il metodo stampaDati();. Eseguendo il programma, otteniamo il seguente output:
Nome: Tizio Cognome: Caio Nato il: 1/1/2003

Provare a modicare il programma, in modo da creare un array che contiene oggetti di tipo Persona e Studente (derivato da Persona) ed eseguire la serializzazione.

83

Parte III

APPENDICI
5
5.1

Una panoramica sul linguaggio


Tipi primitivi

Java fornisce i seguenti tipi primitivi: Tipo


boolean byte short int long float double char

Valore
true o false n & interi nellintervallo [-28 n & ri interi nellinvervallo [-216 n & ri interi nellinvervallo [-232 n & ri interi nellinvervallo [-264 n & ri reali nellinvervallo [-232 n & ri reali nellinvervallo [-264 caratteri
ri

' 28 ( 1] ' 216 ( 1] ' 232 ( 1] ' 264 ( 1] ' 232 ( 1] ' 264 ( 1]

Lunghezza (byte) 1 1 2 4 8 4 8 2

Tabella 2: Tipi primitivi di Java La memorizzazione dei tipi ssa: cio su ogni tipo di macchina la dimensione quella riportata sullultima colonna della tabella 2. Il tipo boolean assume solo i valori di verit true e false: non possono essere usati i valori 1 e 0, come in C/C++! I tipi numerici sono tutti con segno! Il tipo char memorizza i caratteri con la codica Unicode su 2 byte. Java supporta i seguenti caratteri speciali:

) ) ) ) ) )

b backspace; t tabulazione; n newline; r carriage return; " virgole doppie; apice;

5 UNA PANORAMICA SUL LINGUAGGIO

84

)0)

backslash.

Gli array si dichiarano con la seguente sintassi:


String vect[] = new String[10];

Si sta dichiarando un array di stringhe di 10 elementi. Le stringhe in Java non sono un tipo primitivo: esiste la classe String preposta alla loro gestione. Quindi le stringhe sono oggetti e come tali devono essere costruiti (cfr. sezione 3.4).

5.2

Variabili

Le variabili sono dichiarate elencando il tipo, il nome ed, eventualmente, un valore iniziale; le costanti hanno il presso final:
char carattere = C; char nuovaLinea = \n; int numero; final float pi = 3.14; boolean vero = true;

Il simbolo tt= chiamato operatore di assegnamento, perch assegna un valore (che pu essere anche unaltra variabile) ad una variabile.

5.3
5.3.1

Operatori
Operatori Aritmetici

Sono supportate ovviamente le 4 operazioni fondamentali sui numeri: +, -, *, /. Il resto di una divisione si ottiene con %. Le operazioni quali radice quadrata, elevamento a potenza, coseno, etc. sono disponibili, come metodi statici nel package java.lang.Math. Gli operatori di incremento ed decremento sono 121 e (3( . Esempi:
int a = 1, b = 3; int c = a * b; int c = ++a; /* c vale 2 (++a incrementa prima a di 1 e poi esegue lassegnamento) */ c--; // decrementa c di 1 /* */ servono per commenti su pi linee, mentre // usato per commenti su una singola linea.

5 UNA PANORAMICA SUL LINGUAGGIO


5.3.2 Operatori relazionali

85

I valori delle variabili possono essere confrontati con gli operatori:


== uguale; != diverso; < minore stretto; > maggiore stretto; <= minore o uguale; >= maggiore o uguale.

5.3.3

Operatori booleani

Per testare pi valori booleani vengono usati gli operatori:


&& and; || or; ! not.

Le condizioni possono essere combinate con questi operatori. 5.3.4 Operatori su bit

Tali operatori sono:


& and logico; | or logico;

xor;

not;

shift a dx.; shift a sx.

5 UNA PANORAMICA SUL LINGUAGGIO

86

5.4

Blocchi

I blocchi di istruzioni sono delimitati da { e }. Ogni variabile dichiarata in un blocco deve essere inizializzata. possibile dichiarare le variabili in ogni punto del blocco.

5.5

Controllo del usso

Il controllo condizionale avviene con listruzione:


if (condizione) {...} else {...};

Se il blocco composto da una sola istruzione si possono omettere i delimitatori { e }. Esempio:


if ((a > 1) && (b == 2)) a--; else { b++; }

Controlla se a maggiore di 1 e b uguale a 2: in caso affermativo decrementa a di 1, altrimenti incrementa b di 1. N.B. Il controllo viene eseguito in corto circuito: se a minore o uguale di 1, il controllo su b non viene neanche eseguito! I cicli iterativi (loop, di cui non si conosce il numero di volte che dorvranno essere eseguiti, si possono fare in 2 modi:
while (condizione) {...}

oppure
do {...} while (condizione);

Nel primo modo, si esegue subito il test sulla condizione e si itera il blocco di istruzioni nch la condizione valutata true. Nel secondo modo, si esegue prima il blocco di istruzioni e poi si fa il test sulla condizione: la differenza, quindi, fra i due modi che nella seconda forma il blocco di istruzioni viene eseguito almeno 1 volta. Per le iterazioni nite si usa il ciclo for:

5 UNA PANORAMICA SUL LINGUAGGIO


int somma = 0; for (int i=0; i < 10; i++) somma += i;

87

Questo blocco esegue 10 volte listruzione somma += i; che assegna a somma il suo vecchio valore pi i: tale ciclo calcola la somma dei numeri che vanno da 0 a 9. Notare che il ciclo parte da i=0 e nisce a i<10. Inoltre possibile dichiarare ed inizializzare una variabile nel ciclo, come si pu vedere. Tale ciclo pu essere trasformato in un while: int somma = 0; int i = 0; while (i < 10) somma += i; i++; Le selezioni multiple possono essere fatte tramite switch (evitando di avere degli if ... else ... a cascata:
switch (condizione) { case ... : ...; break; case ... : ...; break; . . . // la seguente istruzione opzionale default : ...; break; } break; necessario per interrompere i controlli, quando la condizione stata vericata. Esempio: switch (a) { case 1: b++; break; case 2: b--; break; case 3: b*2; break; case 4: b+=3; break; }

5.6

Operazioni (Metodi)

Le operazioni si dichiarano nel seguente modo:


tipo_di_ritorno nome_della_funzione(argomenti) { ... }

5 UNA PANORAMICA SUL LINGUAGGIO

88

Il tipo_di_ritorno pu essere un tipo primitivo e non (classe) o void: se il tipo_di_ritorno non void, nel blocco si deve usare, come ultima istruzione, return. Gli argomenti possono essere sia tipi primitivi che classi: nel caso si abbiano pi argomenti, essi devono essere separati da una virgola. Il nome_della_funzione non pu essere una keyword di Java (class, super, this, etc.), n un numero, n un segno di punteggiatura. Esempio:
public int f(int a, int b) { int temp = 0; temp = a + b; temp %= 2; return temp; }

Per la discussione sul modicatore di accesso public vedere la sezione 3.2. Questa funzione riceve in ingresso due interi a e b, ne fa la somma, calcola il modulo della divisione per 2 di tale somma e restituisce il risultato. N.B. I parametri in Java sono passati by value (per valore), ci signica che la funzione chiamante riceve il valore delle variabili, facendone una copia temporanea, e non la locazione di memoria (riferimento) in cui tali variabili si trovano; questo vuol dire che una funzione non pu modicare il contenuto dei suoi parametri:
f(...)
temp = 0

a
copia di a

copia di b

f(...)

temp = (a + b) % 2

Figura 32: Passaggio dei parametri ad una funzione

5 UNA PANORAMICA SUL LINGUAGGIO


5.6.1 Il main

89

Ogni programma deve avere un punto di inizio: in Java un programma inizia e nisce nel main, cio in una funzione particolare che identica il ciclo di vita della applicazione che scriviamo. Il main di una applicazione deve risiedere allinterno di una sola classe:
public class Prova { public static void main(String args[]) { System.out.println(Ciao); } }

La semantica del main discussa nella sezione 3. Questo programmino stampa la stringa Ciao a video. 5.6.2 I package

In Java si possono mettere le classi che hanno qualcosa in comune nei package: tutte le classi di una applicazione possono essere raggruppate in una raccolta. Tale linguaggio mette a disposizione molti package: per esempio in java.lang sono denite le classi come String, System, Integer etc., in java.util si trovano le classi di utilit come Vector, Date, etc. etc. Per denire un package si usa la keyword package (Sun consiglia di usare i domini Internet invertiti, essendo unici), p.e:
package com.ciao; public class A { ... }

Si sta denendo il package com.ciao: necessario creare la directory com che contiene la sottodirectory ciao e tutte le classi di tale package devono essere posti in questultima directory. Per usare importare un package in unaltra classe si usa la keyword import:
import com.ciao.A; ...

5 UNA PANORAMICA SUL LINGUAGGIO

90

importa, nella classe corrente, la classe A del package com.ciao. Se si vogliono importare tutte le classi di un package si usa la sintassi:
import com.ciao.*; ...

5.6.3

Gli stream

Java mette a disposione ben 60 classi per gli stream, cio i ussi di byte, i cui usi possono andare dalla gestione dei le alla gestione dei byte di una rete. Ovviamente non si discuteranno tutti gli stream: consultare la documentazione delle API del JDK , reperibile in [1] o il reference [4]. A titolo di esempio vediamo come leggere e scrivere un le di testo. Per la lettura di un le di testo si usa la classe BufferedReader del package java.io:
try { BufferedReader file = new BufferedReader(new FileReader(pippo.txt)); } catch (IOException readExcp) { System.out.println(File non trovato); }

necessario cautelare le operazioni sui le in un blocco try {}...catch {}! A questo punto sufciente un ciclo per scorrere lo stream col metodo readLine():
String line; while ((line = file.readLine()) != null) { System.out.println(line); }

Per la scrittura di un le di testo si usa la classe PrintWriter:


try { PrintWriter file2 = new PrintWriter(new FileWriter(pluto.txt)); } catch (IOException writeExcp) { System.out.println(Non stato possibile scrivere il file); }

e per scriverlo si usano i metodi print(...) o println(...), a seconda se si voglia andare o meno su una nuova linea:

5 UNA PANORAMICA SUL LINGUAGGIO


for (int i=0; i<10; i++) file2.println(i);

91

Scrive sul le file2 i numeri 0,1,. . . ,9. 5.6.4 LI/O a linea di comando

La scrittura di output semplice, infatti basta ricorrere alla classe System di java.lang (tale package importato automaticamente dal compilatore):
System.out.println(...);

Per leggere le informazioni dalla console necessario usare gli stream (cfr. [1]): nella trattazione si usa un metodo pi semplice, cio si legge linput passato al programma a linea di comando. Supponendo che il programma X necessiti di input da tastiera, si usa:
java X ciao,20,b

Si stanno passando ad X i parametri ciao, 20, b che verranno elaborati dal main. 5.6.5 Le eccezioni

Le eccezioni sono un meccanismo utile per evitare che il programma si arresti subito dopo lesecuzione di una istruzione non valida. Il codice da salvaguardare da eventuali errori a run time deve essere posto in un blocco try {...} catch {...}. Eseguendo il seguente programma:
public class Div { // calcola a/b e stampa il risultato void f(int a, int b) { int ris; ris = a/b; System.out.println("Il risultato : "+ris); } // main public static void main(String args[]) {

5 UNA PANORAMICA SUL LINGUAGGIO


Div m = new Div(); m.f(2,0); } }

92

otteniamo il seguente output:


Exception in thread "main" java.lang.ArithmeticException: / by zero at Div.f(Div.java:6) at Div.main(Div.java:14)

Cio la JVM ci informa che stata eseguita una divisione per zero. Cauteliamo allora listruzione di divisione in un blocco try {...} catch {...}:
public class Div { void f(int a, int b) { try { int ris = a / b; System.out.println("Il risultato : "+ris); } catch (Exception e) { System.out.println("Divisione per 0."); } } public static void main(String args[]) { Div m = new Div(); m.f(2,0); } }

Stavolta si ha come output:


Divisione per 0.

Occorre osservare che lesecuzione di un blocco try {...} catch {...} molto pi lento di un if {...} else {...}, quindi bene usare le eccezioni in situazioni in cui non possibile usare un if, come p.e. nella gestione dei le.

5 UNA PANORAMICA SUL LINGUAGGIO


5.6.6 Installazione del JDK

93

Il JDK , cio lambiente di sviluppo per i programmi Java pu essere scaricato dal sito ufciale di Sun Microsystem, allURL:
http://java.sun.com

dalla sezione Download. La versione attuale del JDK la 1.4.0. Una volta scaricato ed installato ( consigliabile installarlo su Linux nella directory /usr/local), necessario settare le variabili di ambiente PATH, CLASSPATH e JAVA_HOME. Su Linux tali variabili vanno settate nel le di congurazione profile che si trova nella directory /etc; ovviamente necessario essere utente root per poter scrivere tale le. sufciente aggiungere queste linee:
export CLASSPATH=.:/usr/local/j2sdk1.4.0_01 export JAVA_HOME=/usr/local/j2sdk1.4.0_01 export PATH=$PATH:/usr/local/j2sdk1.4.0_01/bin

A questo punto per poter compilare un programma Java sufciente scrivere il comando javac seguito dal nome del le. Per esempio, se abbiamo il le Ciao.java, per generare il bytecode basta scrivere a linea di comando:
javac Ciao.java

otteniamo cos il le Ciao.class; per eseguirlo si lancia la JVM :


java Ciao

Notare che stavolta il nome del le Ciao senza estensione.

6 LA LICENZA GNU GPL

94

La licenza GNU GPL

GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free softwareto make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundations software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each authors protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modied by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reect on the original authors reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this,

6 LA LICENZA GNU GPL

95

we have made it clear that any patent must be licensed for everyones free use or not licensed at all. The precise terms and conditions for copying, distribution and modication follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modication".) Each licensee is addressed as "you". Activities other than copying, distribution and modication are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Programs source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modied les to carry prominent notices stating that you changed the les and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modied program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright

6 LA LICENZA GNU GPL

96

notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modied work as a whole. If identiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface denition les, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the

6 LA LICENZA GNU GPL

97

major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution

6 LA LICENZA GNU GPL

98

system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program species a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM

6 LA LICENZA GNU GPL

99

PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source le to most effectively convey the exclusion of warranty; and each le should have at least the "copyright" line and a pointer to where the full notice is found.

6 LA LICENZA GNU GPL

100

<one line to give the programs name and a brief idea of what it does.> Copyright (C) <year> <name of author> This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type show w. This is free software, and you are welcome to redistribute it under certain conditions; type show c for details. The hypothetical commands show w and show c should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than show w and show c; they could even be mouse-clicks or menu itemswhatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program Gnomovision (which makes passes at compilers) written by James Hacker. <signature of Ty Coon>, 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License.

Indice analitico
accesso protetto, 67 ADT, 11 aggregazione, 16, 39 binding dinamico, 26, 74 blocchi di istruzione, 84 classe astratta, 20 base, 16 denizione, 13 derivata, 16 in Java, 27 clonazione, 67 collezioni eterogenee, 62 composizione, 21 confronto, 71 copia, 65 costruttore, 30 dati, 11 downcasting, 73 duplice sottotipo, 21 eccezioni, 89 ereditariet, 16, 42 per il riuso del codice, 17 ereditariet multipla, 21, 58 usso controllo del, 84 Garbage Collector, 9, 30 gerarchia di ereditariet, 19 heap, 30 incapsulamento, 13 in Java, 27 negli oggetti, 14 101 nella aggregazione, 16 nella relazione duso, 15 nella specializzazione, 18 interfacce, 64 interface, 64 JDK, 91 JVM, 6, 30 licenza GPL, 92 line di comando, 89 main, 30 matrimonio, 21 memoria dinamica, 30 metodi astratti, 20 modicatore di accesso, 28 oggetto costruzione in Java, 30 denizione, 14 operatori, 82 aritmetici, 82 booleani, 83 relazionali, 83 su bit, 83 operazioni, 11 astratte, 20 statiche, 29 overloading, 26 overriding, 17 package, 87 passaggio parametri by value, 66 polimorsmo ad hoc, 26 parametrico, 26

INDICE ANALITICO
puro, 25 polimorsmo parametrico simulazione, 62 progettazione, 13 relazioni, 15 aggregazione, 16 ereditariet, 16 ereditariet multipla, 21 relazione un in Java, 42 relazione ha un in Java, 39 relazione usa in Java, 35 uso, 15 riferimento, 30 serializzazione, 77 simulazione della ereditariet, 22 sottoclasse, 16 sottotipo, 16 superclasse, 16 this, 33 tipi primitivi, 81 tipo, 10, 11 tipo a run-time, 26 UML, 13 aggregazione, 16 ereditariet, 17 uso, 15 uso, 15, 35 variabile locale, 37

102

RIFERIMENTI BIBLIOGRAFICI

103

Riferimenti bibliograci
[1] Sun Microsystem, http://java.sun.com [2] Ira Pohl, Object-Oriented Programming Using C++, The Benjamin/Cummings Publishing Company, Inc., 1993 [3] Cay Horstmann - Gary Cornell, Java 2: i fondamenti, McGraw-Hill - Sun Microsystems Inc., 1999 [4] Cay Horstmann, Practical Object Oriented Development in C++ and Java, Wiley Computer Publishing, 1997