Linguaggio Java |
||
per collaborazioni, commenti, critiche, e altro contattateci alla e-mail: clubinfo@libero.it risponderemo al più presto! |
||

Per commentare,
dare suggerimenti o consigli (molto apprezzati) e per segnalare
eventuali errori o
disattenzioni (sempre possibili), puoi inviare una email all'autore
(clicca sul nome sopra). Grazie per la collaborazione!
Quinta lezione
Iniziamo
a lavorare con le classi ed i metodi
Lezione
precedente | Pagina principale | Lezione
successiva
cosa diremo
| 1. | Il centro di tutto sta nelle classi: definizione! |
| 2. | Approfondiamo i mezzi per l'interazione tra gli oggetti: i metodi e la loro implementazione |
nella prossima lezione: Sperimentiamo gli oggetti: introduzione ad Array e liste!
Di creazione di classi abbiamo già parlato in lezioni precedenti, ma mai in modo completo e rigoroso (piccole introduzioni erano indispensabili per procedere con la spiegazione). Ora è giunto il momento di affrontare il problema.
Definizione di
Classe :
Definire una classe è molto semplice, già nelle passate lezioni
abbiamo fatto diversi esempi ed abbiamo accennato molte delle
cose che qui riporteremo (per avere una visione complessiva,
sintetica e completa del concetto e dell'applicabilità delle
classi).
Allora, come si definisce una classe?
Semplicemente utilizzando la parola class seguita dal nome che vogliamo dare alla classe
stessa.
In genere (per default) le classi ereditano tutte dalla classe Object, qualora si
volesse indicare una precisa superclasse dalla quale ereditare,
bisognerebbe (come detto, anche questo, in precedenti lezioni)
aggiungere la parola extends seguita dal nome della superclasse.
| class Albero { ... } class Albero extends Vegetale { ... } |
E dentro?
Ovviamente, perchè la classe possa fare qualcosa ed integrarsi (eventualmente)
in un programma più ampio, deve avere un contenuto. Qual'è il
contenuto delle classi? Ormai dovreste sapere rispondere... Si
tratta di variabili e metodi!
Variabili
Cominciamo con l'introdurre le variabili. Si è già diffusamente
parlato delle variabili locali e delle variabili istanza e di
classe, non staremo a ripetere tutto. Però qualcosa è bene
sottolinearla, in modo da rimarcare alcuni concetti che, anche se
semplici, risultano spesso abbastanza delicati (specie quando
arriva il momento, abbastanza importante, di applicarli).
Qual'è la differenza principale tra variabili locali e variabili
istanza? Il posto dove sono dichiarate! Le prime (variabili
locali) sono dichiarate all'interno di un metodo, e solo lì
hanno valore (all'esterno del metodo sono addirittura sconosciute
ed è possibile creare variabili con il loro stesso nome), le
seconde (variabili istanza) sono definite all'esterno dei metodi
(ma all'interno della classe) ed hanno valore, quindi, su tutta
la classe.
Riporto l'esempio che viene fatto sul libro di testo che uso come
riferimento principale:
In ogni caso non è possibile omettere le parentesi tonde a destra della denominazione della classe; queste possono essere vuote (come nei primi due esempi) o possono contenere dati per l'inizializzazione dell'istanza (il numero ed il tipo di tali argomenti sono definiti in un particolare metodo della classe, che prende il nome di costruttore).
class Bicicletta extends MezzoDiTrasporto {
String tipoBici; // tipo di bicicletta: ad es. Corsa o Mountain
int numRapportiAnt; // numero dei rapporti anteriori (numero corone)
int numRapportiPost; // numero dei rapporti posteriori (numero pignoni)
int rapportoAnt; // rapporto anteriore in uso
int rapportoPost; // rapporto posteriore in uso
}
|
Possiamo fare subito due importanti osservazioni:
Visto che abbiamo intravisto la possibilità
di utilizzo delle costanti, passiamo subito ad introdurle.
Come si definisce una costante all'interno di una classe? Innanzi
tutto diciamo subito che solo le variabili istanza e di classe (quindi
non le variabili locali) possono essere forzate ad essere
costanti. Per far si che una variabile assuma le caratteristiche
di una costante (scusate il gioco di parole, ma non si può che
dire così), è necessario anteporre (nella dichiarazione della
stessa) la parola chiave final (che
sottolinea come il valore assunto sia appunto quello finale, cioè
definitivo e quindi non modificabile).
Ad esempio:
final String tipoBici = "Mountain"; final int maxValue = 10000; |
Infine, come terzo ed ultimo tipo di
variabile, ci sono le variabili di classe. Queste, come in
lezioni precedenti abbiamo già detto (con una trattazione ampia,
della quale qui riassumiamo solamente i punti principali), sono
variabili visibili a tutta una classe e ad ogni sua istanza;
possono quindi essere considerate più generali delle variabili
istanza.
Le variabili di classe possono servire per le comunicazioni tra
istanza distinte della stessa classe o (meglio ancora) per tenere
traccia di una sorta di "stato globale", relativo a
tutta una famiglia di oggetti.
Per dichiarare una variabile di classe, si premette la parola static alla dichiarazione:
| static int
numAutomobili; static final int maxAutomobili = 100; |
Metodi
I metodi, come già detto, definiscono il "comportamento"
degli oggetti (dalla loro creazione.. all'eventuale distruzione!),
e sono (o dovrebbero essere in una buona programmazione)
l'unico
modo per accedere ai
dati. Ma come si lavora con i metodi?
Innanzi tutto vediamo come definirli: la definizione di un metodo
comprende quattro parti essenziali: 1. nome del metodo
stesso, 2. tipo di oggetto (o tipo primitivo) del
valore restituito dal metodo (per chi avesse dimestichezza con la
programmazione procedurale e non ad oggetti.. si tanga conto che
il metodo è molto più simile ad una funzione che ad una
procedura), 3. elenco di parametri (con il
relativo tipo, ovviamente), 4. corpo del metodo (ovvero
l'insieme delle istruzioni che ne definiscono il comportamento).
Diciamo subito che, in realtà, vi possono essere altre parti
nella definizione di un metodo ma, visto che si tratta di
specifiche "avanzate", credo sia opportuno trattarle
successivamente.
Una definizione di metodo
semplice ha la forma:
tipoRisultato nome (tipo1 arg1, tipo2 arg2, tipo3 arg3,... ) {
...
corpo
...
}
|
dove tipoRisultato e tipo1, tipo2,... possono essere tipi primitivi; se il metodo non deve restituire alcun valore (può capitare, naturalmente), si considera come tipoRisultato il termine void (che indica, appunto, l' "assenza" di tipo).
A questo punto ci si
potrebbe chiedere: ma
se necessitiamo che il metodo restituisca più di un valore?
Bene! Basta considerare, come tipo in uscita, un array. Ancora
non abbiamo parlato di array in Java (li introdurremo ed
analizzeremo nella prossima lezione) ma basti pensare che si
tratta di un insieme di variabili (che devono però essere di
tipo omogeneo).
Se il metodo restituisce un
valore (cioè se è stato dichiarato di tipo primitivo o come
array), il suo corpo deve contenere l'istruzione return, che provvede (appunto) alla
restituzione del valore, ecco come:
|
Naturalmente i tre modi (visti sopra) di scrivere questo (semplicissimo) metodo sono equivalenti. Forse il primo è più utile "didatticamente" per capire il significato della parola chiave return ma il terzo è decisamente più semplice e compatto.
Passiamo all'elenco dei parametri. Questo è formato da una serie di dichiarazioni di variabili, separate da virgole, racchiusa tra parentesi tonde e poste a destra del nome del metodo. I parametri diventano variabili locali nel corpo del metodo i cui valori iniziali sono proprio quelli passati all'atto della chiamata del metodo.
Per quel che riguarda il corpo del metodo, ivi possono trovarsi istruzioni, espressioni, chiamate ad altri metodi (anche a metodi di altri oggetti), cicli, ecc...
| Osservazione: | Nella lezione precedente abbiamo parlato di come richiamare
variabili o metodi di una classe (riferendosi ad un
oggetto particolare), e questo viene fatto con la
notazione puntata esprimendo il nome dell'oggetto prima
ed il nome della variabile (o del metodo) dopo. Ma se all'interno
di un metodo fosse necessario riferirsi all'oggetto
stesso (ad esempio per
richiamare un altro metodo o per accedere ad una
variabile istanza o per passare l'oggetto stesso ad un
altro metodo)? In questi casi si utilizza la parola chiave this, che si riferisce (appunto) all'oggetto corrente. Ecco un esempio:
In molti casi però la parola this può essere omessa (e considerata "sottintesa"): le
variabili istanza ed i metodi dello stesso oggetto
possono riferirsi senza necessità di ulteriori
specificazioni (anche se scrivere this, anche in
questi casi, non è errato).
E' bene precisare però che l'omissione
di this è possibile (nei casi indicati) solo
se non esistono variabili (o metodi) locali con lo stesso
nome di quelli richiamati. Inoltre, poichè this è un
riferimento all'istanza corrente di una classe,
naturalmente non può essere utilizzato in caso di metodi
di classe (qeuelli indicati con static). |
Riporto adesso una espressione a mio parere molto significativa in relazione ai metodi ed alla loro importanza all'interno dei programmi Java (e di tutti i programmi object-oriented in generale): "Si può dire che i metodi siano l'elemento principale di ogni linguaggio orientato agli oggetti. Mentre le classi e gli oggetti formano l'intelaiatura, e le variabili di classe ed istanza memorizzano gli attributi e lo stato di una classe o di un oggetto, i metodi definiscono il comportamento di un oggetto e le sue interazione con gli altri" (Lemay, Parkins, Java1.1).
Già in precedenza (specie in questa stessa lezione) sono stati introdotti i metodi e sono state anche fornite le basi necessarie per la stesura dei primi programmi Java. Adesso però cercheremo di focalizzare bene l'attenzione sugli aspetti che rendono i metodi più potenti e flessibili. Ecco quali sono queste caratteristiche:
Analizziamo tali aspetti
separatamente.
Overloading
dei metodi:
E' data la possibilità, in Java, di definire più metodi con lo
stesso nome ma con funzionalità (e quindi corpo) differenti. Per
far questo è però necessario che i metodi non abbiano la stessa
intestazione (si definisce intestazione il nome e l'elenco dei
parametri), quindi che non abbiano gli stessi parametri (in tipo
o numero): Java
infatti utilizza il numero ed il tipo di argomenti per scegliere quale definizione del metodo eseguire.
A che cosa può servire la possibilità di dare stesso nome a
metodi differenti? Non corriamo il rischio di rendere meno chiaro
e leggibile il codice?
Ovviamente no! Così sarebbe se decidessimo di chiamare con lo
stesso nome metodi molto diversi (anche concettualmente) tra loro
(ad esempio, un metodo che serve per contare gli elementi di un
insieme ed un metodo per creare una form sullo schermo!). La
caratteristica di poter "sovraccaricare" un metodo (cioè
creare, con lo stesso nome, metodi diversi) è anzi data al
programmatore per risolvere il problema di dover necessariamente
utilizzare nomi diversi per effettuare operazioni simili.
| Un esempio: Si supponga di voler disegnare un poligono
regolare di N lati. Senza la possibilità di effettuare
overloading sarebbe stato necessario creare metodi con
nomi di questo tipo: disegnaTriangolo
(a,b,c), disegnaQuadrato (a,b,c,d), disegnaPentagono (a,b,c,d,e), ecc.. Con l'overloading è possibile
utilizzare il nome disegna (,,,
...) per ogni poligono
regolare... |
Metodi costruttori:
Abbiamo accennato ad essi già nella lezione precedente.
Sono metodi particolari che vengono utilizzati per "inizializzare" un oggetto al momento della sua creazione (assegnando alcuni
valori di default ai dati, ad esempio). Un metodo costruttore non
può essere richiamato all'interno di un programma, ma viene
utilizzato direttamente (se esiste) dal compilatore Java all'atto
della creazione dell'oggetto in esame.
Vantaggi nell'uso di metodi costruttori:
Definendo metodi costruttori per una classe, si possono impostare
i valori iniziali delle variabili istanza, richiamare certi
metodi, richiamare metodi di altri oggetti o calcolare
determinate proprietà iniziali di un oggetto.. tutte cose svolte
"automaticamente" dal costruttore quando si instanzia
un nuovo oggetto (con l'istruzione, ricordiamo, new). In assenza di
metodi costruttori in una classe, alla creazione di un oggetto
sovente deve seguire l'inizializzazione "esplicita" (non
più automatica, quindi) di tutti i parametri ad esso relativi.
Come si distingue un metodo costruttore dagli altri metodi?
Attraverso due caratteristiche unicamente associabili a tale
particolare metodo:
Facciamo adesso un piccolo esempio
che potete provare voi stessi: class Persona {
String nome;
int anni;
Persona (String n, int a) { // metodo costruttore
nome = n; // inizializza nome
anni = a; // inizializza età
}
void stampaPersona () {
System.out.print ("Ciao, io mi chiamo "+nome);
System.out.println (" ed ho "+anni+" anni.");
System.out.println ("------");
}
public static void main (String arg[]) {
Persona p; // p è di tipo Persona
p = new Persona ("Alessandro",25); // p è Alessandro ed ha 25 anni
p.stampaPersona (); // stampa dati di Alessandro
p = new Persona ("Maria",24); // adesso p è Maria, 24 anni
p.stampaPersona (); // stampa dati di Maria
}
}
Il risultato dell'esecuzione del programma dovrebbe essere il seguente: Ciao, io mi chiamo Alessandro ed ho 25 anni. ------ Ciao, io mi chiamo Maria ed ho 24 anni. ------ |
Possono crearsi più costruttori per una stessa classe, utilizzando il metodo dell'overloading precedentemente descritto. In tal modo è possibile anche differenziare l'inizializzazione dell'oggetto che si intende instanziare, in base ai parametri passati all'istruzione new.
Metodi distruttori (o conclusivi):
Tali metodi sono un po' il "contrario" dei metodi
costruttori: un costruttore inizializza l'oggetto, il metodo
conclusivo viene richiamato invece prima che l'oggetto venga
rimosso (non serve più!).
Per definire un metodo conclusivo per una classe, è necessario
ridefinire (perchè già definito nella classe Object, come
vedremo) il metodo finalize() con la seguente signature:
protect void finalize() throws Throwable {
super.finalize();
}
La parte throws Throwable si riferisce al trattamento di eventuali errori (eccezioni), questo sarà argomento di lezioni più avanzate. |
Il corpo del metodo dovrebbe contenere
tutte quelle operazioni di "pulizia" da compiere prima
della rimozione di un oggetto, cioè: eliminare riferimenti ad
altri oggetti, liberare risorse esterne occupate,... I metodi
conclusivi sono quindi utilizzati
per ottimizzare la rimozione di
un oggetto ma non
lo rimuovono automaticamente (nel senso che è comunque
necessario che vengano eliminati tutti i riferimenti a quell'oggetto,
perchè venga definitivamente rimosso).
Tale metodo, generalmente, non è necessario; lo si utilizza solo
in casi particolari dei quali parleremo in lezioni di livello più
avanzato.
Ridefinizione
di metodi:
Allora, partiamo dal presupposto che, quando si richiama un
metodo, Java cerca la sua definizione nella classe di quest'ultimo
e, se non la trova (con la signature corretta: nome del
metodo, tipo restituito, numero e tipo di parametri, devono
coincidere!), risale la gerarchia delle classi fino a trovare un
metodo con quel nome: l'editarietà
permette di utilizzare un metodo nelle sottoclassi senza doverne duplicare il codice.
Può però essere utile che un oggetto rispondesse a un metodo,
ma "reagisse" in modo diverso (rispetto oggetti di
altre sottoclassi della stessa classe "antenata"). In
tal caso basta "ridefinire" il metodo in esame, cioè
inserire nella classe attuale un metodo con la stessa signature
dell'altro.
Alla chiamata del metodo, la versione definita nella classe
attuale viene individuata prima di eventuali versioni definite in
superclassi (che risultano quindi "nascoste" o "mascherate").
Perchè dovrebbe essere utile ridefinire un metodo già presente in una superclasse?
Per il secondo punto (il primo è già stato esaminato: riscrivendo il metodo, il precedente viene "nascosto"), c'è un modo per evitare di duplicare il codice del metodo della superclasse, ed è quello di utilizzare la parola chiave super (che consente di risalire la gerarchia).
void MetodoProva (String a, int b) {
.... //istruzioni aggiuntive per arricchire il comportamento
super.MetodoProva (a,b); // richiama il codice del metodo (dalla superclasse)
.... //ancora istruzioni per arricchire il metodo
}
|
Osserviamo che la parola super è simile a this, a differenza di quest'ultima, super si riferisce alla superclasse invece che alla classe corrente.
![]()
Questo articolo è
stato scaricato dal Club di informatica |