Sei sulla pagina 1di 120

Programmare e Progettare

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

Non tutti i campi devono essere subito inizializzati.

Non tutte le classi hanno un costruttore.


ESEMPIO REALE
PROGRAMMAZIONE OBJECT ORIENTED (OO) (1)
Questo tipo di programmazione, accennato in Lezione 0, distingue nettamente tra
cosa va fatto e come lo fa.

Il cosa è inteso sotto forma di un insieme di metodi e della relativa semantica


(contratto), che definisce le caratteristiche di un tipo. Un oggetto di una classe è
infatti di un tipo riferimento che ha lo stesso nome della classe.

Il come è definito per un oggetto dalla sua classe di appartenenza che fornisce
l’implementazione dei metodi che l’oggetto supporta.

Un oggetto è un’istanza della classe che lo descrive.


PROGRAMMAZIONE OBJECT ORIENTED (OO) (2)
Quando una classe invoca un metodo su di un oggetto viene esaminata la relativa
classe così da trovare il codice da eseguire.

ES: Se in un una classe Classe1 invochiamo il metodo metodo2() su un oggetto


oggetto2 di tipo Tipo2 definito dalla Classe2 tramite l’invocazione

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.

Classe e Relazione descrivono la realtà in modo generico e formale, definendo i


campi (attributi) delle classi (entità) specificando i valori che queste possono
assumere tramite i domini (tipi). Sono dette intensioni.

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

TIPO VALORE INIZIALE

boolean false

char ‘\u0000’

interi 0

virgola mobile +0.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:

import static pacchetto.NomeClasse.nomeCampo;


ESERCIZI
● Aggiungere alla classe Veicolo un campo statico in grado di contenere il
numero di identificazione del veicolo successivo (inizializzato a 0, avrà lo
scopo di agire come “chiave primaria” e vedremo come) e un campo non
statico in grado di contenere il numero di identificazione di ogni singolo
veicolo (valori diversi per ogni oggetto)
CAMPI FINAL
Esattamente come per le variabili locali interne ai metodi, anche i campi possono
essere dichiarati come costanti, quindi dal valore iniziale definitivo e non più
modificabile.

Essi vengono definiti con il modificatore final, che definisce quindi una proprietà
immutabile di una classe o di un oggetto.

Un campo final senza inizializzazione viene chiamato final vuoto. Bisogna


assicurarsi che esso sia inizializzato al momento della creazione della classe (per
i campi static) o del primo oggetto (per i campi non static).
QUANDO DICHIARARE FINAL?
Il fatto che una proprietà debba essere considerata immutabile dipende dalla semantica
dell’applicazione per la quale la classe è stata progettata.
★ Il campo rappresenta una proprietà immutabile dell’oggetto?
★ Il valore del campo è sempre noto nel momento in cui l’oggetto viene creato?
★ È sempre pratico e appropriato inizializzare il valore del campo nel momento in cui l’oggetto
viene creato?
Se la proprietà viene modificata anche solo sporadicamente allora final non deve essere
utilizzato.
Come mai un campo dovrebbe essere dichiarato final? Questione di ottimizzazione.
DOMANDA: Il campo che contiene il numero di identificazione della classe veicolo potrebbe
essere dichiarato come final?
CONTROLLO DELL’ACCESSO
Uno dei punti di forza della programmazione OO è l’incapsulamento, ovvero la
capacità di racchiudere e occultare i dati all’interno delle classi e dei metodi.
Per ottenere ciò vi è bisogno di poter controllare chi ha accesso a una classe e a
quali membri di essa.
Questo compito è affidato ai modificatori d’accesso.
1. private Classe stessa
2. package +classi dello stesso package
3. protected +proprie sottoclassi
4. public +tutto il resto
METODI
LA NOSTRA CLASSE DI RIFERIMENTO
COSTRUTTORI
Dichiarare una variabile del tipo dell’oggetto che si vuole creare non basta per creare l’oggetto
stesso. Data la classe Persona:

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.

Persona p1 = new Persona();

Se non si riesce ad individuare spazio sufficiente in memoria, viene invocato automaticamente il


garbage collector per liberarne; altrimenti viene interrotta l’esecuzione viene segnalato l’errore.
GARBAGE COLLECTOR
Gli oggetti a cui non si fa più riferimento vengono chiamati garbage poiché non è
più possibile accedervi. Il garbage collector è una routine offerta dalla JVM che
identifica e rimuove i garbage.

Garanzia forte: se l’oggetto può essere raggiunto in un qualsiasi modo, allora non
viene etichettato come garbage.

La rimozione non viene sempre effettuata (è il collector a decidere quando è il


caso).

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

Un metodo abstract non può essere static, final, synchronized, strictfp.

È raccomandato listare i modificatori nell’ordine sopra presentato.


METODI STATICI
Metodi che vengono invocati rispetto ad una classe e non ad una sua istanza.
Vengono detti metodi della classe.

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).

Attenzione! Il metodo può comunque lavorare su valori passati come argomento


che non derivano a membri statici!
ESEMPIO METODO STATICO

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).

All’invocazione il chiamante deve fornire un argomento del tipo appropriato per


ognuno dei parametri dichiarati per il metodo. Non è necessario rispettare
strettamente il tipo se sono possibili conversioni implicite!

ES: byte a int, involucro a tipo base.

Invocare metodi statici non richiede il riferimento, riducendo l’invocazione a:

nomeMetodo(argomenti)
IL METODO TOSTRING (1)
Tutte le classi ereditano i metodi della classe Object, tra cui toString().

Normalmente esso stampa “NomeClasse@HashCode”, ma è possibile ridefinire il


comportamento in ogni classe facendo in modo che si produca un oggetto String
che contenga informazioni disposte nel modo più utile nella corrente situazione.

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.

Un metodo termina se avviene uno dei seguenti casi:

➢ Si esegue un’istruzione di return


➢ Si raggiunge la fine del metodo
➢ Si solleva un’eccezione non intercettata

al che il controllo torno al chiamante.

Se un metodo restituisce un risultato, esso deve essere singolo.


RESTITUIRE RISULTATI MULTIPLI
È possibile restituire risultati multipli usando una delle seguenti scappatoie:

★ Restituire riferimenti ad oggetti che immagazzinano i risultati sotto forma di


campi
★ Sfruttare 1+ parametri che referenziano oggetti per memorizzare i risultati
all’interno di essi (ES: Array)
★ Restituendo un array di risultati
PERCORSI DI ESECUZIONE
Quando si usano costrutti quali gli if-else, non tutto il codice di un metodo viene
eseguito durante una invocazione.
All’interno di un metodo che restituisce un valore, ogni percorso di esecuzione
deve:
➔ o restituire un valore che possa essere assegnabile ad una variabile del tipo
uguale a quello di ritorno
➔ o sollevare un’eccezione.
VALORI DEI PARAMETRI
I parametri vengono passati ai metodi in una modalità detta “per valore”.

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:

❏ Parametro: Variabile che il metodo usa. Il suo valore deriva da quello


dell’argomento.
❏ Argomento: Valore di una variabile usata per invocare un metodo.
PASSAGGIO DI TIPI RIFERIMENTO
Nel caso in cui il parametro fosse un riferimento ad un oggetto, ad essere passato
sarebbe il riferimento all’oggetto e non l’oggetto stesso.

In questo modo è possibile modificare l’oggetto direttamente attraverso il metodo


senza alterarne il riferimento effettivamente passato.
CONTROLLO DELL’ACCESSO CON I METODI
Si potrebbe volere che alcuni campi siano accessibili dall’esterno in sola lettura, senza
offrire possibilità di modifica. Non ci sono keyword al riguardo, ma…
Si può usare la keyword private così che non si possa accedere dall’esterno in nessun
modo se non tramite l’uso di un opportuno metodo public get che restituisca il valore.
Per permettere la modifica, si può definire un metodo set che la permetta.
In generale, i campi vanno SEMPRE dichiarati private (o protected, massimo package).
Il corpo del metodo può anche includere altre righe di codice per permettere maggiore
controllo nell’accesso, ad esempio chiamate ad altri metodi che permettono di registrare
quanto accaduto (chi ha avuto accesso, quando, perché, etc…)
Sfruttiamo al massimo il principio di information hiding!
METODI ACCESSORI

STATO METODO GET METODO SET

INACCESSIBILE // //

SOLA LETTURA getNomeCampo() //

SOLA SCRITTURA // setNomeCampo()

SCRITTURA E LETTURA getNomeCampo() setNomeCampo()


METODI GET
Permettono di accedere ai valori dei campi.

Convenzionalmente scritti come:

public tipoCampo getNomeCampo() {

return nomeCampo;

Oltre all’istruzione di return è possibile aggiungere altre istruzioni così da rendere


l’accesso controllato e sicuro.
METODI SET
Permettono di modificare il valore dei campi.

Convenzionalmente scritti come:

public void setNomeCampo(tipoCampo val) {

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:

La segnatura di un metodo è la combinazione del suo nome + tipo e numero dei


suoi parametri.

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.

La segnatura non comprende il tipo restituito né la lista delle eccezioni.


ESERCIZI
● Aggiungere due metodi dal nome turn alla classe Veicolo: uno che richieda il
numero di gradi di cui far sterzare il veicolo e l’altro che richieda a scelta una
delle due costanti (da definire) Veicolo.TURN_LEFT (gira di 90° a sinistra,
quindi -90) e Veicolo.TURN_RIGHT (+90).
IMPORT STATICO
Generalmente si fa riferimento ad un membro utilizzando il nome della classe cui esso
appartiene come per System.out o Math.sqrt.

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.

import static java.lang.Math.sqrt; //Importa il metodo sqrt

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:

★ Di contratto o tipo: La sottoclasse acquisisce il tipo della superclasse e quindi


può essere utilizzata polimorficamente in tutti i punti in cui si può usare la
superclasse. (Specializzazione)
★ Di implementazione: La sottoclasse acquisisce l’implementazione della
superclasse in termini di campi e metodi accessibili.

Estendere una classe comporta entrambe.


CLASSE DI RIFERIMENTO
ESERCIZI
● Scrivere la classe di riferimento della slide precedente.
● Analizzare la classe scritta e comprenderne il significato. Domandare se
qualcosa non è chiaro.
● Commentare i metodi descrivendo ciò che fanno a parole.
ATTRIBUTO
La classe di riferimento rappresenta un attributo che può assumere un qualsiasi
tipo di valore.

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.

Le variabili di tipo Object possono riferirsi a un qualunque oggetto, sia esso


un’istanza di una classe o un array (che è in realtà un’istanza della classe Arrays).
IL PROCESSO DI ESTENSIONE
Vogliamo estendere la nozione di attributo in modo da poterla utilizzare per memorizzare
attributi che si riferiscono a colori: possono essere stringhe contenenti il nome del colore
o descrizioni del colore.
La descrizione può essere sia un nome come “rosso” o “giallo” da ricercare all’interno di
una tabella, sia valori numerici da decodificare. Effettuare la decodifica potrebbe essere
costoso, tanto da volerlo fare una sola volta.
Estendiamo la classe Attributo così da creare una classe ColoreAttributo che supporti un
metodo in grado di restituire un oggetto ScreenColor (rappresenta il colore) dopo averlo
decodificato.
CONVENZIONE: In genere le sottoclassi dovrebbero contenere il nome della
superclasse.
COLOREATTRIBUTO - LA CLASSE
COLOREATTRIBUTO - OSSERVAZIONI
La classe ColoreAttributo estende Attributo: questo lo rendiamo noto al compilatore con la keyword
extends:

sottoclasse extends superclasse

La sottoclasse sa fare tutto ciò che sa fare la superclasse, inoltre aggiunge nuovi comportamenti.

Le funzioni di ColoreAttributo sono fondamentalmente le seguenti:

➔ 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

Se aggiungessi altre sottoclassi...

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.

Si stabilisce che si sta usando l’implementazione del metodo della superclasse e


non quella corrente, che invece usa la keyword this.

Costruttore: super()

Altri metodi: super.nomeMetodo()


THIS…?
Si può rimandare la scelta del costruttore della superclasse usando la già vista
keyword this.

Dichiarare this fa in modo che venga usato un costruttore della sottoclasse, che
verrà scelto in base agli argomenti passati:

Usa un costruttore della sottoclasse: this()


COSTRUTTORI IN SOTTOCLASSI (2)
Nel caso la prima istruzione eseguibile di un costruttore non sia un’invocazione di
un costruttore della superclasse né della sottoclasse, allora viene invocato il
costruttore no-arg della superclasse prima che venga eseguita la prima
istruzione del costruttore considerato.
In pratica viene implicitamente inserito un super() all’inizio del blocco.
Se non è presente un costruttore no-args (un unico caso, quale?) allora va
invocato esplicitamente un altro costruttore.
I COSTRUTTORI NON VENGONO EREDITATI! Se la sottoclasse vuole costruttori
nella stessa forma della superclasse, allora deve dichiararli esplicitamente. (Anche
con un solo super())
INVOCARE UN COSTRUTTORE ESTESO
Quando viene invocato il costruttore di una sottoclasse:

1. Invocazione di un costruttore della superclasse.


2. Inizializzazione dei campi mediante i rispettivi inizializzatori.
3. Esecuzione del corpo del costruttore.
1 - COSTRUTTORE DELLA SUPERCLASSE
L’invocazione viene eseguita in modo implicito o esplicito.

I. Se è indicata un’invocazione tramite this allora si segue la catena di tali


invocazioni fino a che non si incontra un’invocazione implicita o esplicita di un
costruttore della superclasse.
II. Viene effettivamente invocato il costruttore della superclasse, eseguito
anch’esso tramite le 3 fasi che stiamo descrivendo fino a che troviamo il
costruttore della classe Object.

Ogni espressione valutata come parte di un’invocazione esplicita di costruttore


non può riferirsi ad alcuno dei membri dell’oggetto corrente.
2 - INIZIALIZZAZIONE DEI CAMPI
Vengono eseguiti tutti gli inizializzatori e nello stesso ordine in cui sono stati
dichiarati.

Possibile riferirsi ad altri membri dell’oggetto corrente purché dichiarati prima.


3 - ESECUZIONE DEL CORPO
Si eseguono le istruzioni del corpo del costruttore.

Se esso è stato invocato esplicitamente, il controllo tornerà al costruttore che lo


aveva invocato così che possano essere eseguite le parti restanti che ne
compongono il corpo.

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

0 Inizializzazione valori a default 0 0 0

1 Invocato costruttore Y() 0 0 0

2 Invocato costruttore X() (super) 0 0 0

3 Invocato costruttore Object() 0 0 0

4 Inizializzazione campi X 0x00ff 0 0

5 Esecuzione costruttore di X 0x00ff 0 0x00ff

6 Inizializzazione campi Y 0x00ff 0xff00 0x00ff

7 Esecuzione costruttore di Y 0x00ff 0xff00 0xffff


ESERCIZI
● A partire dalla classe Veicolo, scrivere una sua sottoclasse chiamata
PasseggeriVeicolo che possa aggiungere la capacità di contare il numero di
posti a disposizione nella vettura e quelli effettivamente occupati. Dotare la
classe di un metodo main che possa creare un certo numero di tali oggetti e
di visualizzarli (ridefinire quindi toString()).
● Considerare X e Y. Aggiungere istruzioni di visualizzazione che permettano di
tener traccia dei valori assunti dalle maschere. Aggiungere quindi un main ed
eseguirlo per vedere i risultati ottenuti utilizzando %x per visualizzare gli interi
in formato esadecimale.
○ printf(“%x”, numero)
OVERRIDE!
Quando si estende una classe è possibile aggiungervi nuovi membri o ridefinire
quelli esistenti.
Nella classe ColoreAttributo il metodo setVal() è stato ridefinito e non
sovraccaricato:
➢ Overload: Più metodi dotati dello stesso nome ma con diversa segnatura.
➢ Override: Sostituisce l’implementazione del metodo della superclasse con
una nuova implementazione. Le segnature sono le stesse, ma il tipo restituito
può variare (in modo specifico).
Chiamare Attributo.setVal() usa l’implementazione della superclasse.
Chiamare ColoreAttributo.setVal() usa l’implementazione della sottoclasse.
VARIARE IL TIPO RESTITUITO
L’override ha la stessa segnatura del metodo della superclasse, ma può far
cambiare il tipo in modo specifico:

❏ Se il tipo restituito è di tipo riferimento, allora si può dichiarare un tipo di


ritorno che sia un sottotipo di quello dichiarato dal metodo della superclasse.
❏ Se il tipo restituito è di tipo primitivo, allora non si può variare. (Errore in
compilazione!)

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.

Quando si accede ad un campo, è il tipo dichiarato per il riferimento a essere


utilizzato.

Rendiamo il tutto più comprensibile attraverso un esempio:


ESEMPIO ACCESSO
REGOLE DI ACCESSO
A prescindere dal riferimento passato, si invoca sempre il metodo della classe
estesa e mai quello della superclasse.

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.

All’interno di un metodo un qualsiasi riferimento a un campo è sempre relativo al


campo dichiarato nella classe in cui è dichiarato il metodo; se non è presente la
dichiarazione di quel campo allora ci si riferisce al campo ereditato (show si
riferisce al campo SuperShow.str in SuperShow e al campo ExtendShow.str in
ExtendShow).
ACCESSIBILITÀ E OVERRIDING
Un metodo può subire override solo se è accessibile.

Metodi non accessibili non vengono ereditati e quindi non possono essere
ridefiniti.

Se una sottoclasse riscrive lo stesso tipo di ritorno e la stessa segnatura di un


metodo private della sua superclasse, i metodi risultanti sono scorrelati tra loro.

In pratica, invocare un metodo private porta sempre all’esecuzione


dell’implementazione della classe corrente.
LA MIA FORMA FINALE!
Classi e metodi definiti final non possono essere estesi da nessun’altra classe.
Garantisce la massima forma di sicurezza, restringendo estremamente l’uso che
se ne può fare.
Rendere una classe final (final class nomeClasse) implica che nessuna classe
potrà essere dichiarato come nomeClasseEstesa extends nomeClasse.
Rendere un metodo final (final tipoRit nome(params)) implica che nessuna
classe che estende quella in cui è definito possa farne override.
Bisogna essere sicuri di accettare le conseguenze che comporta dichiarare final.
Rendere final quando possibile è un’ottima forma di ottimizzazione.
LA MIA FORMA ASTRATTA!
Una classe astratta è una classe della quale è definita solo una parte della sua
implementazione, lasciando che siano le sottoclassi a definirne gli aspetti.

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.

I tipi costituiscono l’unità fondamentale della progettazione orientata agli oggetti.

Le classi permettono di definire tipi.

Le interfacce permettono di definire dei tipi in modo astratto come collezioni di


metodi o di ulteriori altri tipi. Esse non sono dotate di implementazione e non
possono produrre oggetti (non sono istanziabili).
IMPLEMENTARE INTERFACCE
Una classe può ampliare i propri tipi implementando una o più interfacce.
Le interfacce hanno molte più implementazioni possibili delle classi.
Una classe può implementare più interfacce ma ereditare un’unica implementazione: una classe
può estendere una sola classe.
Per una classe specifica, si definiscono supertipi:
❖ Le classi che essa estende (superclasse)
❖ Le interfacce che essa implementa (superinterfacce)
La classe in questione viene chiamata sottotipo.
Un sottotipo deve dare implementazione per tutti i metodi offerti dalle sue superinterfacce, o
lasciarli abstract.
INTERFACCE STANDARD
Le interfacce definiscono proprietà che tanti oggetti appartenenti a classi diverse
possono avere. L’oggetto “è in grado di fare” una determinata cosa, rappresentata
dall’interfaccia.

Alcune interfacce contenute nel package standard di Java sono:

❏ Cloneable I sottotipi supportano la clonazione


❏ Comparable I sottotipi possiedono una relazione di ordinamento
❏ Runnable I sottotipi rappresentano unità di lavoro eseguibili in thread
❏ Serializable I sottotipi possono essere scritti come flussi di byte
COMPARABLE
Interfaccia dotata di un unico metodo, dichiarata come:

La dichiarazione è simile a quella che ci si aspetterebbe per una classe.

compareTo chiede un parametro di tipo T che confronta con l’oggetto corrente


(presumibilmente dello stesso tipo) restituendo un numero maggiore, minore o
uguale a 0 in base all’ordine dei due oggetti.

Come si può rendere comparabile una classe, nella pratica?


ESEMPIO DI CLASSE COMPARABILE
USO DELLE INTERFACCE
Così come le classi, le interfacce producono nuovi nomi di tipo, è quindi possibile
dichiarare variabili di tali tipo; ad ES:

Comparable<Punto> p1;

In questo modo è possibile applicare metodi a tali variabili ignorando le specifiche


della classe a cui appartengono (sono comunque applicabili solo ad oggetti dello
stesso tipo!). Ad esempio, si può definire un sorter per ordinarli.
CHE METODI POSSONO INVOCARE?
I riferimenti di tipo interfaccia possono essere usati per accedere solo ai membri dell’interfaccia
specifica. Per utilizzare i metodi della classe dell’oggetto su cui è definito il riferimento (Punto per
Comparable<Punto>) bisogna effettuare una forzatura esplicita di tipo.

Una forzatura di questo tipo può essere effettuata nel modo:

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).

Devono essere inizializzate.

Se ci fosse bisogno di dati condivisi e modificabili si può utilizzare una costante


denominata che faccia riferimento a un oggetto che contiene effettivamente i dati
ed usare esso tale riferimento come tramite: accedo al riferimento costante e lo
uso per trovare l’oggetto, quindi lavoro su esso.
METODI DI INTERFACCIA
Tutti i metodi dichiarati all’interno di un’interfaccia sono implicitamente abstract
(omesso per convenzione). Essi sono dichiarati come:

tipoRestituito nome(params);

Si noti:

➔ Il corpo del metodo è unicamente composto da un ‘;’


➔ Non sono ammessi modificatori che non siano abstract poiché quasi tutti sono
legati all’implementazione! Le loro implementazioni nelle classi invece li
ammettono.
MODIFICATORI DI INTERFACCIA
Questi modificatori sono applicabili alla dichiarazione dell’interfaccia e non ai suoi
metodi.

❖ public Accessibile pubblicamente, pena l’accesso solo da package


❖ abstract Implicito e omesso
❖ strictfp Usa aritmetica stretta

Se si vogliono usare o esplicitare i modificatori presentati, farlo nell’ordine


proposto (non obbligatorio, ma best practice!)
INTERFACCE CHE ESTENDONO INTERFACCE
Le interfacce possono estendere più interfacce, a differenza delle classi.

public interface nomeInterfaccia extends nomeInterfaccia1, nomeInterfaccia2

Tutti i metodi definiti da nomeInterfaccia1 e nomeInterfaccia2 (superinterfacce)


sono implementati da nomeInterfaccia (sottointerfaccia).

È possibile accedere alle costante e ai metodi delle diverse superinterfacce in


modi diversi.
EREDITARE E ADOMBRARE COSTANTI
Come abbiamo detto, la sottointerfaccia eredita tutte le costanti delle superinterfacce.

Se la sottointerfaccia dichiara una costante di una delle sue superinterfacce, la adombra.

All’interno di una sottointerfaccia e ogni oggetto che la implementa far riferimento ad una costante fa
riferimento alla costante definita nella sottointerfaccia.

Alle costanti ereditate si può ancora accedere tramite nomeSuperinterfaccia.costante.

Se una classe implementa un’interfaccia, può richiamare le costanti ereditate come se fossero suo tramite
nomeSottointerfaccia.costante.

Attenzione: adombrare i campi è poco utile nelle interfacce!

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).

L’interfaccia contiene dichiarazioni multiple dello stesso metodo, ma in ognuna


delle classi che la implementano ci può essere una sola implementazione del
metodo in sé.
OVERRIDE CON INTERFACCE (2)
Se un’interfaccia eredita più di un metodo dotato della stessa segnatura (da 1+
interfacce) questo non costituisce ambiguità: la classe ne implementa una sola
versione!

Attenzione: se i metodi ereditati hanno segnatura uguale ma tipi restituiti diversi,


allora uno dei tipi restituiti deve essere sottotipo di tutti gli altri o si incorre in errore
di compilazione!

L’implementazione deve definire un metodo che restituisca il sottotipo comune.


OVERLOAD CON INTERFACCE
In caso si faccia overload di un metodo in un’interfaccia, la sottoclasse dovrà
definire un’implementazione per TUTTE le forme sovraccaricate.

Un errore di compilazione si presenta se:

❏ Un metodo dichiarato si discosta da uno ereditato solo per il tipo restituito


❏ Due metodi ereditati differiscono solo per il tipo restituito
INTERFACCE E CLASSI ASTRATTE
Quale è meglio utilizzare?

➔ Le interfacce permettono ereditarietà multipla: è possibile implementarne più


contemporaneamente; una classe può estendere solo una classe per volta a
prescindere che sia astratta o meno.
➔ Una classe abstract può disporre di un’implementazione parziale, di sezioni
protette, di metodi statici, etc…; le interfacce hanno solo costanti e metodi
pubblici.
ESERCIZI
● Riscrivere FonteEnergetica usando un’interfaccia invece che una classe
astratta.
CENNI AI PACKAGE
PACKAGE
I package sono unità SW che raggruppano classi e interfacce correlate che
mettono a disposizione servizi per lavorare verso uno scopo.
Essi possono essere situati all’interno di un file di archivio insieme ad un suo file di
descrizione e gli utenti possono accedervi.
Caratteristica fondamentale: creano spazi di nomi evitando conflitti tra nomi di tipi:
spesso ci troveremo a costruire classi con lo stesso nome ma che lavorano con
classi differenti per le quali offrono servizi diversi: il package spezza le ambiguità.
Ulteriore protezione: identificatori utilizzabili dentro un package spesso non sono
utilizzabili all’esterno.
DICHIARARE PACKAGE
package nomePackage;

Va inserito nell’header space e dichiara che tutte le classi e interfacce definite nel
file corrente sono parte dell package nomePackage.

Possibile farne una sola!

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!

Il nomePackage segue degli standard, ma variano di ambiente in ambiente.


IMPORT
Per utilizzare i tipi offerti dal package, ogni volta che li si richiama lo si deve fare
nella forma
nomePackage.nomeTipo
Possibile evitare di scrivere ogni volta il nomePackage tramite la keyword import
nell’header space.
import nomePackage.nomeTipo; (oppure *)
Sì, è ciò che abbiamo fatto con java.util.Scanner!
java.util è un package.
GESTIONE DEGLI ERRORI
GESTIONE DEGLI ERRORI
La gestione degli errori è critica: risolvere i casi non voluti a livello di codice può portare ad una
pesantezza estrema!
Le eccezioni controllate permettono di gestire gli errori costringendo a pensare cosa farne a
seconda del punto in cui esse possono insorgere. Un'eccezione controllata non gestita ne causa
l’evidenziazione in fase di compilazione!
Un metodo che si accorge dell’insorgere di una condizione d’errore inattesa solleva (throws)
un’eccezione.
Esse possono essere quindi intercettate (caught) e gestite da una speciale parte di codice, così
da permettere poi il normale proseguimento dell’esecuzione.
Un’eccezione non intercettata (uncaught) causa il termine del flusso d’esecuzione, ma prima che
esso termini si ha l’opportunità di gestire la situazione nel miglior modo possibile.
ECCEZIONI
Sono oggetti con tipo, metodi e dati così che possano effettuare dei report o garantire il recupero
di tipi specifici di eccezione.
Derivano generalmente dalla classe Exception, a sua volta figlia della classe Throwable.
(Vedremo questi concetti in un’apposita lezione)
Il paradigma di uso è try-catch-finally:
★ try: si prova a fare qualcosa
★ catch: se il try solleva un’eccezione, essa viene intercettata
★ finally: riporta al normale flusso di esecuzione o al percorso relativo alla gestione
dell’eccezione a seconda di come è andato il try
Se l’esecuzione di un metodo provoca il sollevamento di eccezioni controllate bisogna dichiarare
i tipi di tali eccezioni all’interno di una clausola throws a seguito della segnatura del metodo.
CONTROLLATE VS NON CONTROLLATE
Le eccezioni controllate rappresentano quelle condizioni che, anche se insolite,
possono ragionevolmente verificarsi e vanno in qualche modo gestite. Dichiararle
esplicitamente permette al compilatore di assicurare che il metodo sollevi solo tali
eccezioni e non altre.
Le eccezioni non controllate rappresentano condizioni, generalmente,
rispecchiano errori nella logica del programma e che non possono essere
fronteggiate adeguatamente durante l’esecuzione: ad esempio
ArrayIndexOutOfBoundExcepion (indice al di fuori dei limiti dell’array) o
NullPointerException (la nostra migliore amica). Sta al programmatore assicurarsi
che il codice non contenga errori logici.
SOMMARIO
In questa lezione abbiamo visto:
● Programmazione OO
● Campi
● Costruttori
● Garbage Collector
● Metodi
● Accesso alle classi
● Estensione delle classi (ereditarietà e polimorfismo)
● Interfacce
● Cenni a package
● Cenni a eccezioni

Potrebbero piacerti anche