Questo modificatore rappresenta un fondamentale e potente comando di
Java. Con static, la programmazione ad oggetti, trova un punto di
incontro con quella strutturata, ed il suo uso, deve essere quindi
limitato a situazioni in cui realizzi in una concreta utilità.
Potremmo tradurre il termine static con "condiviso da tutte le
istanze della classe", oppure "della classe". Per quanto detto, un
membro statico ha la caratteristica di poter essere utilizzato
mediante una particolare sintassi del tipo:
nomeClasse.nomeMembro
in luogo di:
nomeOggetto.nomeMembro
Anche senza istanziare la classe, l’utilizzo di un membro statico,
provocherà il caricamento in memoria della classe contenente il
membro in questione, che quindi, condividerà il ciclo di vita con
quello della classe.
- metodi statici
Un esempio di metodo statico, è il metodo sqrt() della classe Math,
che viene chiamato tramite la sintassi:
Math.sqrt(numero)
Math, è quindi il nome della classe e non il nome di un’istanza di
quella classe. La ragione per cui la classe Math dichiara tutti i
suoi metodi statici è abbastanza facilmente comprensibile. Infatti,
se istanziassimo due oggetti differenti dalla classe Math, ogg1 e
ogg2, i due comandi:
ogg1.sqrt(4);
e
ogg2.sqrt(4);
produrrebbero esattamente lo stesso risultato (2). Effettivamente
non ha senso istanziare due oggetti di tipo matematica, che come si
sa, è unica.
N.B.: un metodo dichiarato static e public, può considerarsi una
sorta di funzione, e perciò, questo tipo di approccio ai metodi va
accuratamente evitato.
- variabili statiche (di classe)
Una variabile statica, essendo condivisa da tutte le istanze della
classe, assumerà appunto lo stesso valore per ogni oggetto di una
classe. Di seguito viene presentato un esempio:
public class ClasseDiEsempio
{
public static int a=0;
}
public class ClasseDiEsempioPrincipale
{
public static void main (String args[])
{
System.out.println("a= "+ClasseDiEsempio.a);
ClasseDiEsempio ogg1=new ClasseDiEsempio();
ClasseDiEsempio ogg2=new ClasseDiEsempio();
ogg1.a=10;
System.out.println("ogg1.a = " +ogg1.a);
System.out.println("ogg2.a = " +ogg2.a);
ogg2.a=20;
System.out.println("ogg1.a = " +ogg1.a);
System.out.println("ogg2.a = " +ogg2.a);
}
}
L’output di questo semplice programma sarà:
a = 0
ogg1.a = 10
ogg2.a = 10
ogg1.a = 20
ogg2.a = 20
Come il lettore può chiaramente notare, se un'istanza modifica la
variabile statica, essa risulterà modificata anche relativamente
all’altra istanza. Infatti, essa è condivisa dalle due istanze, ed
in realtà risiede nella classe. Una variabile di questo tipo,
potrebbe ad esempio essere utile per contare il numero di oggetti
istanziati da una classe (per esempio incrementandola in un
costruttore). Quindi il modificatore static, prescinde dal concetto
di oggetto, e lega strettamente le variabili al concetto di classe,
che a sua volta si innalza ad un qualcosa di più che un semplice
mezzo per definire oggetti. Per questo motivo a volte ci si
riferisce alle variabili d’istanza statiche, come "variabili di
classe".
N.B.: una variabile dichiarata static e public, può considerarsi una
sorta di variabile globale, e perciò, questo tipo di approccio alle
variabili va accuratamente evitato.
N.B.: una variabile statica non viene inizializzata al valore nullo
del suo tipo al momento dell’istanza dell’oggetto.
- inizializzatori statici
Il modificatore static, può anche essere utilizzato per marcare un
semplice blocco di codice, che viene a sua volta ribattezzato
inizializzatore statico. Anche questo blocco, come nel caso dei
metodi statici, potrà utilizzare variabili definite al di fuori di
esso se e solo se dichiarate statiche. In pratica un blocco statico
definito all’interno di una classe, avrà la caratteristica di essere
chiamato al momento del caricamento in memoria della classe stessa,
addirittura, prima di un eventuale costruttore. La sintassi è
semplice, e di seguito è presentato un semplice esempio:
class EsempioStatico
{
static int a=10;
public EsempioStatico()
{
a+=10;
}
static
{
System.out.println("valore statico = "+a);
}
}
Supponiamo di istanziare un oggetto di questa classe, mediante la
seguente sintassi:
EsempioStatico ogg=new EsempioStatico();
Questo frammento di codice produrrà il seguente output:
valore statico = 10
Infatti quando si istanzia un oggetto da una classe, questa deve
essere prima caricata in memoria. È in questa fase di caricamento
che viene eseguito il blocco statico, e di conseguenza stampato il
messaggio di output. Successivamente viene chiamato il costruttore
che incrementerà il valore statico.
N.B.: un blocco di codice statico, è alla base della possibilità di
rendere codice Java indipendente dal database a cui si interfaccio
tramite JDBC.
N.B.: concludendo, l’utilizzo di questo modificatore dovrebbe quindi
essere legato a soluzioni di progettazione avanzate.
Unità didattica 9.6)
- Il modificatore abstract
Il modificatore abstract può essere applicato a classi e metodi.
- Metodi astratti
Un metodo astratto, è un metodo che non implementa un proprio blocco
di codice e quindi il suo comportamento. In pratica, un metodo
astratto, non ha il blocco di codice, ma termina con un punto e
virgola. Un esempio di metodo astratto potrebbe essere il seguente:
public abstract void dipingiQuadro();
Ovviamente, questo metodo, non potrà essere chiamato, ma potrà
essere soggetto a riscrittura (override) in una sottoclasse.
Inoltre, un metodo astratto, potrà essere definito solamente
all’interno di una classe astratta. In altre parole, una classe che
contiene anche un solo metodo astratto, deve essere dichiarata
astratta.
- Classi astratte
Una classe dichiarata astratta, non può essere istanziata. Quindi,
il programmatore che ha intenzione di marcare una classe con il
modificatore abstract, deve essere consapevole a priori che da
quella classe non saranno istanziabili oggetti. Consideriamo ad
esempio la seguente classe astratta:
abstract class Pittore
{
. . .
public abstract dipingiQuadro();
. . .
}
Questa classe ha senso inserirla in un sistema in cui l’oggetto
Pittore, può essere considerato troppo generico per definire un
nuovo tipo di dato da istanziare. Supponiamo che per il nostro
sistema, è assolutamente fondamentale conoscere lo stile pittorico
di un oggetto Pittore, e siccome non esistono pittori capaci di
dipingere con un qualsiasi tipo di stile, non ha senso istanziare
una classe Pittore. Sarebbe corretto popolare il nostro sistema di
sottoclassi di Pittore non astratte come PittoreImpressionista e
PittoreNeoRealista. Queste sottoclassi devono ridefinire il metodo
astratto dipingiQuadro (almeno che non si abbia l’intenzione di
dichiarare astratte anche esse), e tutti gli altri eventuali metodi
astratti ereditati. Effettivamente, la classe Pittore deve
dichiarare un metodo dipingiQuadro, ma a livello logico, non sarebbe
giusto definirlo favorendo uno stile piuttosto che un altro. Nulla
vieta però ad una classe astratta, di implementare tutti suoi
metodi. Il modificatore abstract, per quanto riguarda le classi,
potrebbe essere considerato come l’opposto del modificatore final.
Infatti, una classe final non può essere estesa, ma una classe
dichiarata abstract deve essere estesa.
N.B.: il grosso vantaggio che offre l’implementazione di una classe
astratta, è che "obbliga" le sue sottoclassi ad implementare un
comportamento. A livello architetturale, la progettazione di classi
astratte è un strumento fondamentale.
N.B.: nel Modulo 6, dove abbiamo presentato i vari aspetti del
polimorfismo, avevamo fatto un esempio in cui una classe Veicolo
veniva estesa da varie sottoclassi (Auto, Nave… ), che
reimplementavano i metodi ereditati (accelera, e decelera) in
maniera adatta al contesto. La classe Veicolo era stata presentata
nel seguente modo:
class Veicolo
{
public void accelera()
{
. . .
}
public void decelera()
{
. . .
}
}
e forse il lettore più riflessivo si sarà anche chiesto cosa avrebbe
mai potuto dichiarare all’interno di questi metodi. Effettivamente,
come accelera un veicolo? Come una nave o come un aereo? La risposta
più giusta a questo quesito è che non c’è risposta! La risposta è
indefinita. Un veicolo sicuramente può accelerare e decelerare, ma,
in questo contesto, non si può dire come. La soluzione più giusta
appare quella di dichiarare i metodi astratti:
class Veicolo
{
public abstract void accelera();
public abstract void decelera() ;
}
Il polimorfismo continua a funzionare in tutte le sua manifestazioni
(riguardare l’esempio).
Unità didattica 9.7)
- Interfacce
Un’interfaccia è un’evoluzione del concetto di classe astratta. Per
definizione, un’interfaccia ha tutti i metodi astratti, e tutte le
variabili dichiarate public, static e final. Un’interfaccia per
essere utilizzata ha bisogno di essere in qualche modo estesa, non
potendo ovviamente, essere istanziata. Si può fare qualcosa di più
rispetto al semplice estendere un interfaccia: la si può
implementare. L’implementazione di un interfaccia da parte di una
classe, consiste nell’ereditare tutti i metodi e fornire loro un
blocco di codice, e si realizza posponendo alla dichiarazione della
classe la parola chiave implements, seguita dall’identificatore
dell’interfaccia che si desidera implementare. La differenza tra
l’implementare un’interfaccia ed estenderla, consiste essenzialmente
nel fatto che, mentre possiamo estendere una sola classe alla volta,
possiamo invece implementare infinite interfacce, simulando di fatto
l’ereditarietà multipla, ma senza gli effetti collaterali negativi
di essa.
Possiamo notare che siccome un’interfaccia non può dichiarare metodi
non astratti, non c’è bisogno di marcarli con abstract (e con
public). Di seguito è presentato un esempio per familiarizzare con
la sintassi:
interface Saluto
{
public static final String ciao = "Ciao";
public static final String buongiorno = "Buongiorno";
. . .
public void saluta();
}
Un’interfaccia, essendo un’evoluzione di una classe astratta, non
può essere istanziata. Potremmo utilizzare questa interfaccia in
questo modo:
class SalutoImpl implements Saluto
{
public void saluta()
{
System.out.println(ciao);
}
}
- Ereditarietà multipla
Le interfacce sono il mezzo tramite il quale Java riesce ad
implementare l’ereditarietà multipla. L’implementazione di una
interfaccia da parte di una classe, richiede come condizione
imprescindibile, l’implementazione di ogni metodo astratto ereditato
(altrimenti bisognerà dichiarare la classe astratta).
L’implementazione dell’ereditarietà multipla da parte di una classe,
si realizza posponendo alla dichiarazione della classe la parola
chiave implements, seguita dagli identificatori delle interfacce che
si desiderano implementare, separati da virgole. Il lettore potrebbe
non recepire immediatamente l’utilità e la modalità d’utilizzo delle
interfacce quindi, di seguito, è presentato un significativo esempio
forzatamente incompleto, e piuttosto complesso se si valutano le
informazioni finora fornite:
Come il lettore avrà probabilmente intuito, abbiamo definito
un’applet. In Java, un’applet è una qualsiasi classe che estende la
classe Applet, e che quindi non può estendere altre classi.
Nell’esempio abbiamo implementato due interfacce che contengono
metodi particolari che ci permetteranno di gestire determinate
situazioni. In particolare l’interfaccia MouseListener, contiene
metodi che gestiscono gli eventi che hanno come generatore il mouse.
Quindi, se implementiamo il metodo astratto mousePressed(),
ereditato dall’interfaccia, definiremo il comportamento di quel
metodo, che gestirà gli eventi generati dalla pressione del mouse.
Notare che implementiamo anche un’altra interfaccia chiamata
Runnable, tramite i cui metodi possiamo gestire eventuali
comportamenti di processi paralleli che Java ha la possibilità di
utilizzare con il multithreading. In pratica, in questo esempio, con
la sola dichiarazione stiamo inizializzando una classe non solo ad
essere un applet, ma anche a gestire eventi generati dal mouse, ed
un eventuale gestione parallela dei thread.
- Conclusioni
Concludendo, invitiamo il lettore giunto sino a questo punto con la
coscienza di aver appreso correttamente le nozioni impartitegli
sinora, ad affrontare finalmente il mondo della programmazione Java,
nella maniera più corretta possibile. Come egli stesso avrà avuto
modo di constatare, la profonda comprensione dei concetti
presentati, anche se da un lato può sembrare che rallenti la pratica
implementativa, dall’altro favorirà una maggiore velocità di
apprendimento per i concetti più complessi. Approcciare alle
interfacce grafiche, alle applet, alle servlet, agli EJB ed a tutti
gli atri tipi di applicazioni che sono basati su Java, ora, non
dovrebbe essere più un problema.
Torna alla Pagina>> 1