Documenti di Didattica
Documenti di Professioni
Documenti di Cultura
con Java.
Lezione 2 - Classi e Oggetti
CONCETTI FONDAMENTALI
CLASSE
La classe è l’unità base della programmazione Java.
Il suo scopo è quello di definire la struttura degli oggetti e i meccanismi con cui
questi possono essere costruiti a partire dalla definizione che ne dà la classe.
Vengono descritti sia i dati (campi) che il comportamento (metodi).
★ Campi: Variabili che contengono dati associati alla classe e ai suoi oggetti e
servono a memorizzare il risultato dei calcoli effettuati dalla classe.
★ Metodi: Contengono il codice eseguibile della classe e sono costituiti da
istruzioni. L’ordine e il modo della loro invocazione dirige il flusso del
programma.
STRUTTURA DI UNA CLASSE
CORPO DELLA CLASSE
Il come è definito per un oggetto dalla sua classe di appartenenza che fornisce
l’implementazione dei metodi che l’oggetto supporta.
oggetto2.metodo2()
ciò che accade è che si andrà ad interpellare tale Classe2 e si andrà a recuperare
il codice eseguibile a partire dalla segnatura di metodo2.
PARALLELISMO TRA OO E DB
Per chi mastica bene il concetto di database relazionale, è possibile paragonare
la relazione che intercorre tra Classe e Oggetto con quella che sussiste tra
Relazione e Tabella.
Oggetto e Tabella sono casi specifici che rappresentano oggetti che assumono
determinati valori. Essi rispettano l’intensione e sono detti estensioni.
CLASSE PERSONA E OGGETTI
MODIFICATORI DI UNA CLASSE
La dichiarazione di una classe può essere preceduta da modificatori della
classe: keyword che ne definiscono proprietà particolari.
Una classe può avere diversi modificatori associati, elencati prima della propria
dichiarazione in un ordine qualsiasi, ma è consigliato mantenere una certa
coerenza all’interno del codice (Ancora: più è leggibile e meglio è)
➔ public: A una classe di questo tipo si può accedere liberamente. Chiunque
può dichiarare riferimenti a oggetti della classe o accedere ai suoi membri
pubblici. Senza questo modificatore, solo i membri dello stesso package
potranno accedervi.
➔ abstract: Classe considerata incompleta e quindi non istanziabile.
➔ final: Classe non estendibile
CAMPI
CAMPI
I campi (o fields, o attributi) sono le variabili della classe.
I campi possono essere inizializzati: in questo caso gli oggetti prodotto avranno
valori iniziali per tali campi. Una volta istanziato l’oggetto, il cambiamento dei valori
dei campi riguarda solo l’oggetto per il quale i campi sono stati cambiati. Tali
campi sono detti variabili dell’istanza.
MODIFICATORI DEI CAMPI
Così come ogni dichiarazione, la dichiarazione dei campi può essere preceduta da
diversi modificatori che ne controllano diverse proprietà:
❖ modificatori di accesso
❖ static
❖ final
❖ transient (Lezione IO)
❖ volatile (Lezione Esecuzione concorrenziale)
Un campo non può essere sia final che volatile.
Si raccomanda di seguire l’ordine riportato in precedenza se si dichiarano
modificatori multipli per lo stesso campo!
VALORI DI DEFAULT DEI CAMPI
boolean false
char ‘\u0000’
interi 0
riferimento null
ESERCIZI
● Scrivere una semplice classe Veicolo dotata di campi per le seguenti
informazioni:
○ Velocità
○ Direzione (gradi)
○ Nome Proprietario
● Scrivere una classe LinkedList dotata di un campo di tipo Object e un
riferimento al successivo elemento LinkedList all’interno della lista.
CAMPI STATICI
Un campo statico è un campo che condivide la propria istanza (valore corrente) con tutti
gli oggetti generati dalla classe contenente quel campo.
Si dichiarano utilizzando il modificatore static e vengono chiamate variabili della classe.
Ne esiste una singola copia a prescindere dal numero di oggetti creati.
All’interno di una classe è possibile riferirsi al campo statico direttamente, mentre
dall’esterno bisognerà fargli precedere il nome della classe.
ES: La classe Integer ha 3 campi statici (MIN_VALUE, MAX_VALUE, SIZE). Sono tutti
dichiarati nella seguente forma:
public static int MIN_VALUE = -2147483648
Per riferirsi a tale campo si dovrà scrivere: Integer.MIN_VALUE
ESEMPIO CAMPO STATICO
IMPORTAZIONE CAMPI STATICI
È possibile raffinare il codice risparmiandosi il dover scrivere ogni volta il nome
della classe del campo statico tramite una tecnica conosciuta con il nome di
importazione statica:
Essi vengono definiti con il modificatore final, che definisce quindi una proprietà
immutabile di una classe o di un oggetto.
Persona p1;
Crea la variabile che conterrà il riferimento, ma questo deve essergli passato dall’operatore new,
il cui ruolo è quello di generare un riferimento ad un oggetto creato dal costruttore. Ogni classe
ha un costruttore implicito che non richiede argomenti (no-arg) chiamato costruttore di default
che usa i valori iniziali definiti dalla classe.
Garanzia forte: se l’oggetto può essere raggiunto in un qualsiasi modo, allora non
viene etichettato come garbage.
Il garbage collector evita la presenza dei dangling pointers visti nella Lezione 0.
ESERCIZI
● Creare un metodo main per la classe Veicolo che crei (tramite il costruttore di
default Veicolo()) un certo numero di veicoli e visualizzi i valori dei loro campi.
● Creare un metodo main per la classe LinkedList che crei (tramite il costruttore
di default Veicolo()) un certo numero di oggetti Veicolo e li posizioni all’interno
di nodi consecutivi della lista.
MOLTEPLICI COSTRUTTORI
Una classe può avere un qualsiasi numero di costruttori grazie al principio
dell’overloading. Tutti i costruttori sono metodi dotati dello stesso nome della
classe.
Si noti che è possibile ridefinire il costruttore di default Classe() con una propria
versione, esplicitando tale metodo nel codice.
I costruttori sono blocchi di istruzioni utilizzabili per inizializzare un oggetto prima
che new restituisca il riferimento ad esso.
La dichiarazione è costituita dal nome della classe seguito da un elenco (anche
vuoto) di parametri tra () e da un corpo di istruzioni racchiuso tra {}.
PERCHÉ VOLERNE DI PIÙ?
★ Alcune classi possono non avere uno stato iniziale plausibile se prive di
parametri (quindi il costruttore di default non basta).
★ Fornire uno stato iniziale è al tempo stesso plausibile e conveniente nel
momento in cui si costruiscono certi tipi di oggetti (preferiamo farlo subito).
★ Costruire un oggetto è un’operazione potenzialmente costosa e quindi è bene
gli oggetti siano dotati di uno stato iniziale corretto subito dopo essere stati
creati.
★ Un costruttore non public permette di limitare il numero di utenti che possono
creare oggetti attraverso esso.
COSTRUTTORE COPIA
Un costruttore copia è un costruttore che permette di creare oggetti con le
stesse caratteristiche di quelli passati come argomento.
Non sono molto utilizzati poiché esistono metodi più raffinati e che danno luogo a
meno ambiguità.
ESERCIZI
● Aggiungere due costruttori alla classe Veicolo: uno no-arg e uno che richiede
il nome del proprietario iniziale. Modificare il main in modo fargli generare lo
stesso output che generava prima ma che sia realizzato usando i nuovi
costruttori.
● Determinare il costruttore/i costruttori adeguati per LinkedList e implementarli.
DICHIARARE METODI
La dichiarazione di un metodo è composta da due parti:
➔ Intestazione: modificatori (opzionali) + tipi parametro (opzionali) + tipo restituito +
segnatura(nome + lista parametri) + throws(opzionale)
➔ Corpo: serie di istruzioni racchiuse tra parentesi graffe
MODIFICATORI DEI METODI
❖ modificatori di accesso
❖ abstract
❖ static
❖ final
❖ synchronized (Lezione Esecuzione Concorrente)
❖ strictfp
Offre servizi utili a tutti gli oggetti di una classe, ma può accedere solo ai campi
static e agli altri metodi static della classe poiché l’accesso a tutti i membri non
static è possibile solo tramite un riferimento ad un oggetto che però non è
presente all’interno di un metodo di questo tipo (non presente il this - vedi più
avanti).
Si ricorda che all’interno del codice della classe dichiarante il metodo statico è
possibile invocare il metodo semplicemente scrivendo
String s = razzaAttuale();
Mentre le altre classi dovranno chiamare anche il nome della classe che lo
dichiara:
String s = Persona.razzaAttuale();
ESERCIZI
● Aggiungere un metodo statico alla classe Veicolo che sia in grado di restituire
il più alto numero di identificazione usato fino ad ora.
INVOCAZIONE DEI METODI
I metodi sono invocati come operazioni sugli oggetti attraverso la combinazione
riferimento.nomeMetodo(argomenti).
nomeMetodo(argomenti)
IL METODO TOSTRING (1)
Tutte le classi ereditano i metodi della classe Object, tra cui toString().
In genere è buono poter disporre i valori dei campi dell’oggetto in un modo tale per
cui il metodo della classe String split() possa facilmente estrarre le informazioni.
IL METODO TOSTRING (2)
Il metodo toString() di un oggetto ha una particolarità: esso viene invocato per
ottenere una String nei casi in cui l’oggetto venga utilizzato, mediante l’operatore
+, all’interno di un’espressione di concatenazione tra stringhe.
ESERCIZI
● Aggiungere un metodo toString alla classe Veicolo
● Aggiungere un metodo toString alla classe LinkedList
METODI CON NUMERO VARIABILE DI ARGOMENTI
Abbiamo visto nella scorsa lezione come l’ultimo parametro può essere dichiarato come:
tipo… nomeParametro
I tre puntini vengono chiamati ellissi.
Questo tipo di parametri permettono di invocare il metodo con un numero variabile di
parametri, fatto per cui metodi che fanno uso di questa tecnica vengono conosciuti come
varargs.
Quando viene invocato il metodo, il compilatore fa corrispondere ogni argomento al
parametro fornito e, quando legge l’ellissi, mette tutti i restanti argomenti in un array del
tipo dichiarato.
Si noti che il numero di argomenti variabili può anche essere 0.
ESEMPIO VARARGS
ESECUZIONE E USCITA DA UN METODO
Quando un metodo viene effettivamente invocato, il controllo passa ad esso e
vengono eseguite le sue istruzioni.
I valori dei parametri che il metodo usa come variabili sono copie di quelli degli
argomenti usati per invocare il metodo. Alterare i valori dei parametri non ha
conseguenze sui valori degli argomenti!
DISAMBIGUAZIONE:
INACCESSIBILE // //
return nomeCampo;
nomeCampo = val;
Anche qui è possibile arricchire il metodo con altre istruzioni per potenziare il
controllo.
ESEMPIO METODI ACCESSORI
ESERCIZI
● Rendere private i campi della classe Veicolo e aggiungere metodi accessori
per i campi. Quali campi dovrebbero avere metodi set e quali no?
● Rendere private i campi della classe LinkedList e aggiungere metodi
accessori per i campi. Quali campi dovrebbero avere metodi set e quali no?
● Aggiungere alla classe Veicolo un metodo cambiaVelocità in grado di
modificare la velocità corrente del veicolo in relazione a un valore fornito
(+tot, -tot: controllare che il valore finale abbia senso!) e un metodo stop in
grado di portare la velocità a 0.
WHAT’S THIS?!
this è uno speciale riferimento a oggetto utilizzabile all’interno di metodi non statici. Esso indica
l’oggetto corrente che ha invocato il metodo.
ES: Per p.getNome(), this indicherebbe l’oggetto p.
Spesso passato come argomento ad un metodo che usa lo stesso oggetto per invocare un altro
metodo.
ES: p.clona(this); //Crea un clone di p
Usato solo quando strettamente necessario. Utile per disambiguare nei costruttori.
OVERLOADING
Già discusso, richiamiamo:
Due metodi possono avere lo stesso nome se hanno segnature diverse, quindi se
cambia il numero e/o il tipo dei suoi parametri.
Gli argomenti passati al nome del metodo istruiscono il compilatore riguardo quale
versione del metodo invocare.
Indicare il nome della classe ogni volta può risultare scomodo e difficile da leggere.
Per alleggerire il tutto, è possibile comunicare al compilatore che ogni volta che ci
riferisce ad un metodo, ogni volta ci si riferisce al metodo di una classe specifica tramite
l’uso dell’import static.
Attenzione ad usare * per i conflitti di nome! Esistono regole per gestire le competizioni,
ma se non le si conosce meglio importare solo quanto serve! (Così come sempre)
ESERCIZI PROPOSTI
Per tutti gli esercizi seguenti, definire oltre le specifiche un metodo main per poter testare le classi.
● Creare una classe Potenza che abbia come campo la base, che sia dotata di un costruttore che
inizializzi la base e i seguenti metodi:
○ get e set
○ pow(int n) - Metodo che eleva la base alla potenza n e che restituisce il valore calcolato
○ toString() - Converte la base usata in stringa.
● Creare una classe T che esprima un orario come HH:MM:SS (le parti dell’orario sono i campi) che
abbia 2 costruttori: uno no-args che inizializza a 0:0:0 e uno che richiedere tutte le parti dell’orario
tenendo conto che deve essere valido (facoltativo: creare altri costruttori). Creare i seguenti metodi:
○ get e set
○ isT(T orario) - Verifica che l’orario passato come argomento sia valido
○ addT(T orario) - Aggiunge l’orario T all’orario riferito. ES: 1:12:30 + 1:48:30 = 3:1:0.
○ subT(T orario) - Sottrae l’orario T all’orario riferito. ES: 1:12:30 - 1:48:30 = 23:36:0
○ reset() - Imposta l’orario a 0:0:0. Usare il metodo set.
○ toString() - Converte l’orario in stringa. Notare che le parti a singola cifra vengono stampate con uno 0 davanti (Es: 1:2:35
verrà convertito in “01:02:35”)
ESTENSIONE DELLE CLASSI
SUPERCLASSE, EREDITÀ, POLIMORFISMO
Nella programmazione OO è possibile estendere il comportamento di una classe
esistente così da poter utilizzare il codice scritto per quella classe in altre classi estese
(sottoclassi). La classe che estende è nota come superclasse.
Quando si estende una classe allo scopo di crearne una nuova, quest’ultima eredita i
campi e i metodi della superclasse.
Una sottoclasse può essere utilizzata in tutti quei punti dove venga richiesto un oggetto
della classe originale: questa caratteristica è nota come polimorfismo.
L’insieme dei membri (campi e metodi) accessibili dall’esterno di una classe e la
descrizione del comportamento che ci si aspetta da loro vengono riuniti nel contratto
della classe: ciò che il progettista della classe ha stabilito relativamente al suo
comportamento.
EREDITARIETÀ
Si presenta in due forme:
La classe Attributo è sottoclasse implicita di Object, così come tutte le altre classi.
Classi che estendono direttamente object sono dette sottoclassi dirette o di
primo grado.
La sottoclasse sa fare tutto ciò che sa fare la superclasse, inoltre aggiunge nuovi comportamenti.
➔ Fornisce 3 costruttori: due rispecchiano quelli della superclasse e uno accetta direttamente un
oggetto ScreenColor.
➔ Ridefinisce e overloadda setVal(): esso può usare l’oggetto colore nel momento in cui il valore
cambia.
➔ Fornisce un nuovo get: getColore() restituisce un valore corrispondente alla descrizione del colore
decodificata come oggetto ScreenColor.
GERARCHIA DI ATTRIBUTO
TonoColoreAttributo
è una sottoclasse di
secondo ordine, o
indiretta.
COSTRUTTORI IN SOTTOCLASSI (1)
Una sottoclasse contiene campi di 2 nature:
1. Campi ereditati dalla superclasse
2. Campi introdotti dalla sottoclasse
Per poter costruire un oggetto della classe estesa bisogna inizializzare
correttamente entrambi gli insiemi delle variabili di stato.
Solo la superclasse sa come inizializzare correttamente il proprio stato, quindi i
costruttori della sottoclasse devono delegare la costruzione dello stato della
classe che estendono invocando (implicitamente o esplicitamente) uno dei
costruttori di quest’ultima.
SUPER!
super è una keyword disponibile per tutti i metodi non statici di una classe e si
comporta come un riferimento all’oggetto corrente visto come istanza della
superclasse.
Costruttore: super()
Dichiarare this fa in modo che venga usato un costruttore della sottoclasse, che
verrà scelto in base agli argomenti passati:
Processo ripetuto fino a che il corpo del costruttore specificato all’interno della
keyword new non è stato eseguito completamente.
INVOCAZIONE DI UN COSTRUTTORE - ESEMPIO
TRACCIA DELL’ESECUZIONE
Passo Che succede? xMask yMask fullMask
Si noti che per i metodi varargs, il parametro P… è considerato come P[] e quindi
si può scrivere il metodo override di un metodo che prende un array come
parametro come metodo varargs. Vale anche il contrario ma è sconsigliato.
RIDEFINIRE MODIFICATORI
I metodi override sono dotati di propri modificatori d’accesso, modificando quelli
previsti dalla superclasse ma solo per renderli “più ampi”.
● private può diventare package, protected, public
● protected può diventare package o public
● package può diventare public
● public deve rimanere tale
synchronized e strictfp possono variare a piacimento.
Un metodo override può essere dichiarato final e non è (ovviamente) possibile
ridefinire un metodo final.
Il metodo override può essere reso abstract.
ADOMBRAMENTO
I campi non possono essere ridefiniti, ma possono essere adombrati.
Ridichiarare un campo con lo stesso nome di uno della superclasse non permette
di accedervi usando quel nome: per farlo bisogna usare super o un altro
riferimento al tipo della superclasse.
Che siano campi o metodi, se essi sono statici il ridefinirli altro non porta che ad
adombrarli.
ESERCIZI
● Per la classe ColoreAttributo, scrivere una classe ScreenColor e ridefinire il
metodo di Object equals() per entrambe le classi.
ACCESSO AI MEMBRI EREDITATI
Quando si invoca un metodo attraverso un riferimento a oggetto, la classe
corrente dell’oggetto permette di capire quale implementazione del metodo vada
impiegata.
Per quanto riguarda i campi, è sempre il tipo del riferimento e non la classe
contenente l’oggetto a determinare a quale campo della classe si accede.
Metodi non accessibili non vengono ereditati e quindi non possono essere
ridefiniti.
Questo tipo di classi è utile quando parte del comportamento è definito per tutti o quasi gli oggetti
di un certo tipo e ha senso per alcune classi particolari e non per la superclasse.
Una classe di questo tipo va dichiarata come abstract class nomeClasse, così come ogni suo
metodo di cui non viene fornita implementazione è dichiarato come abstract tipoRit
nome(params). Si noti che la presenza di anche un solo metodo abstract richiede che anche la
classe sia dichiarata come tale.
Ogni sottoclasse può ridefinire come abstract i metodi non abstract della superclasse.
Una classe abstract non può costruire oggetti: è una fabbrica immaginaria.
ESERCIZI
● Modificare la classe Veicolo così che sia dotata di un riferimento ad un
oggetto FonteEnergetica ad essa associato all’interno del proprio costruttore.
● Scrivere la classe FonteEnergetica come classe astratta: essa può essere o
un Serbatoio o una Batteria.
● Dotare FonteEnergetica di un metodo astratto empty() e implementarlo nelle
classi Serbatoio e Batteria. Il metodo empty() porta la fonte a 0.
● Dotare Veicolo di un metodo start() che assicura che la fonte energetica usata
come combustibile non sia esaurita.
INTERFACCE
CLASSI E TIPI
Le classi costituiscono l’unità fondamentale nella programmazione in Java.
Comparable<Punto> p1;
Punto p = (Punto) p1
Attenzione! Non sempre un cast di questo tipo è accettato! Se si effettua un cast da superclasse
a sottoclasse (widening) non si perde informazione, mentre al contrario (narrowing) sì! Il
compilatore non piange solo se è sicuro che la conversione sia sicura!
Si possono sempre invocare i metodi di Object in quanto anche le interfacce sono figlie di tale
classe!
DICHIARARE INTERFACCE
modificatori interface nomeInterfaccia { … }
Un’interfaccia può dichiarare solo 3 tipi di membri:
➢ costanti (campi)
➢ metodi (astratti)
➢ classi e interfacce innestate
Tutti i membri sono implicitamente public poiché membri non tali non hanno molto
senso. Per convenzione il modificatore public viene omesso ogni volta che
dovrebbe presentarsi!
È comunque possibile dichiarare tali membri come non pubblici.
COSTANTI DI INTERFACCIA
Gli unici campi che un’interfaccia può dichiarare sono costanti che sono
implicitamente dichiarate come public, static e final (omessi per convenzione).
tipoRestituito nome(params);
Si noti:
All’interno di una sottointerfaccia e ogni oggetto che la implementa far riferimento ad una costante fa
riferimento alla costante definita nella sottointerfaccia.
Se una classe implementa un’interfaccia, può richiamare le costanti ereditate come se fossero suo tramite
nomeSottointerfaccia.costante.
Ancor più attenzione: ereditare due costanti con lo stesso nome dà luogo ad ambiguità e va evitato,
altrimenti si deve sempre specificare la superinterfaccia contenente la costante che si vuole utilizzare.
OVERRIDE CON INTERFACCE (1)
La sottointerfaccia eredita tutti i metodi dichiarati nelle sue superinterfacce. Se un
metodo dichiarato all’interno di una superinterfaccia ha la stessa segnatura di un
metodo ereditato, la nuova dichiarazione fa override di tutte le dichiarazioni
esistenti (che restituiscono tipo uguale o covariante).
Va inserito nell’header space e dichiara che tutte le classi e interfacce definite nel
file corrente sono parte dell package nomePackage.
Se non dichiarato, il file viene considerato parte di uno speciale package senza
nome: questo rende più facile sviluppare piccole applicazioni come quelle viste
fino ad ora!