ARRAY,
METODI
STATICI
E
VARIABILI
STATICHE
IN
JAVA
Domenico
Sacc
1.
Memorizzazione
di
Array
Come
abbiamo
visto
nella
dispensa
sulle
stringhe,
in
Java
possibile
utilizzare,
oltre
ai
tipi
base
(quali
ad
esempio,
int,
double,
char,
boolean),
anche
tipi
derivati
definiti
tramite
apposite
classi.
Nel
caso
di
String,
tale
classe
gi
disponibile
tra
quelle
messe
a
disposizione
da
una
qualsiasi
piattaforma
Java.
Molte
altre
sono
disponibili
e
agevolmente
utilizzabili.
Inoltre
un
programmatore
pu
crearne
delle
proprie.
La
definizione
di
classi
in
Java
non
rientra
tra
gli
obiettivi
del
corso.
A
una
variabile
di
tipo
base
viene
assegnata
una
locazione
di
memoria
in
cui
viene
memorizzato
il
valore
assunto
dalla
variabile
durante
lesecuzione
di
un
programma.
Una
locazione
di
memoria
viene
assegnata
anche
ad
una
variabile
di
tipo
derivato
(che
chiamata
oggetto)
ma,
in
essa,
non
viene
memorizzato
il
valore
(che
nel
caso
di
una
stringa
o
di
un
array
non
prefissato
ma
variabile
durante
lutilizzo)
ma
un
puntatore
(cio
lindirizzo,
che
occupa
tipicamente
4
byte)
ad
unaltra
area
di
memoria
dove
effettivamente
memorizzato
il
valore;
inoltre
viene
memorizzato
il
numero
di
byte
occupati
dalla
memorizzazione
del
valore.
Per
gestire
efficacemente
le
aree
di
memorie
da
destinare
ai
valori
degli
oggetti,
la
memoria
dati
di
un
programma
viene
suddivisa
in
due
parti:
lo
stack
(pila),
dove
vengono
memorizzate
le
variabili
di
tipo
base
e
i
puntatori
degli
oggetti,
e
lo
heap
(mucchio)
dove
sono
memorizzati
i
valori
degli
oggetti.
Ad
esempio,
se
vengono
dichiarate
le
variabili
char
A
=a;
String
S
=
ciao,
esse
vengono
memorizzate
nel
seguente
modo.
Nello
stack
la
variabile
A
occupa
una
locazione
di
2
byte
per
rappresentare
direttamente
il
suo
valore
(si
ricorda
che
un
carattere
occupa
2
byte)
e
la
stringa
S
occupa
due
locazioni,
ciascuna
di
4
byte,
la
prima
contenente
lindirizzo
della
locazione
nello
heap
in
cui
memorizzato
il
valore
della
stringa,
e
la
seconda
il
numero
di
byte
occupati
dalla
stringa
pari
a
8
byte:
(4
caratteri)
(2
byte),
cio
la
stringa
memorizzata
nello
heap
dallindirizzo
100
fino
allindirizzo
107.
In
effetti
la
memorizzazione
di
una
stringa
un
po
pi
complicata
poich
la
classe
String
speciale
e
ha
delle
particolarit
che
la
distinguono
dalle
altre
classi
e
che
sono
state
introdotte
per
la
rilevanza
che
oggi
assume
la
gestione
di
stringhe
in
molte
applicazioni
(si
pensi
a
GOOGLE).
Una
variabile
pu
essere
definita
di
tipo
derivato
anche
attraverso
lutilizzo
degli
array,
che
rappresentano
vettori
di
elementi
tutti
dello
stesso
tipo
(che
pu
essere
un
tipo
base
o
una
classe
o
anche
un
ulteriore
array
come
accade
per
una
matrice
che
un
array
di
array
di
interi
o
double).
Se
adesso
definiamo
un
array
di
3
interi
come
int
[
]
V
=
{1,
2,
3};
la
variabile
V
occupa
nello
stack
due
locazioni
di
4
byte
ciascuna,
la
prima
per
memorizzare
lindirizzo
del
primo
elemento
dellarray
nello
heap
e
la
seconda
il
numero
di
byte
necessari
per
memorizzare
tutti
gli
elementi
pari
a
12:
(3
interi)
(4
byte).
vale
variabili
sono
ora
complessivamente
memorizzate
nel
seguente
modo
(si
ricorda
che
un
intero
occupa
4
byte),
cio
il
vettore
memorizzato
nello
heap
dallindirizzo
108
fino
allindirizzo
119.
2.
Tipi
Array
Come
gi
anticipato,
in
Java
possibile
utilizzare
tipi
derivati
tra
cui
gli
array,
che
rappresentano
vettori
di
elementi
tutti
dello
stesso
tipo.
Ad
esempio:
int
[
]
X;
char
[
]
Y;
double
[
]
Z;
String
[
]
W;
definiscono
X
come
array
di
interi,
Y
come
array
di
caratteri,
Z
come
array
di
double
e
W
come
array
di
stringhe.
Per
essere
utilizzabile,
un
array
deve
essere
dimensionato
attraverso
un
apposito
comando
new.
Ad
esempio:
X
=
new
int
[20];
stabilisce
che
X
ha
20
elementi
individuati
con
appositi
indici
che
vanno
da
0
a
19.
Quindi
X[0]
il
primo
elemento
e
X[19]
lultimo
elemento.
Si
noti
che,
per
ogni
i=0..19,
X[i]
di
tipo
intero
cio
un
elemento
di
un
array
ha
come
tipo
quello
base
dellarray.
E
possibile
anche
stabilire
la
dimensione
dellarray
nel
momento
della
sua
definizione:
int
X
=
new
int
[20];
E
anche
possibile
anche
inizializzare
larray
nel
momento
della
sua
definizione:
int
Y
=
{1,2,3};
Larray
Y
viene
definito
con
dimensione
3
e
vengono
assegnati
ai
suoi
3
elementi
rispettivamente
i
valori:
1,
2
e
3.
Successivamente
alla
sua
definizione,
possibile
ridimensionare
un
array.
Ad
esempio
int
X
=
new
int
[20];
//
definizione
dellarray
X
e
sua
inizializzazione
//
attenzione
che
X
inizializzato
ma
i
suoi
20
elementi
non
ancora
[Link](X[5]);
//
viene
stampato
un
valore
indeterminato
valore
non
inizializzato
[Link](X[20]);
//
viene
segnalato
errore:
lelemento
[Link]
non
esiste
for
(
int
i
=0;
i
<
20;
i++
)
X[i]=i;
//
ora
vengono
inizializzati
gli
elementi
di
X
[Link](X[5]);
//
viene
stampato
il
valore
5
[Link](X[40]);
//
errore:
lelemento
[Link]
non
esiste
for
(
int
i
=0;
i
<
20;
i++
)
[Link](X[i]);
//
vengono
stampati
i
venti
elementi
di
X
X
=
new
int
[40];
//
viene
ridefinito
larray
X
che
ora
ha
40
elementi
//
attenzione
che
i
20
elementi
precedenti
ora
sono
cancellati
for
(
int
i
=0;
i
<
40;
i++
)
X[i]=i;
//
ora
vengono
inizializzati
i
nuovi
40
elementi
di
X
for
(
int
i
=0;
i
<
40;
i++
)
[Link](X[i]);
//
vengono
stampati
i
nuovi
quaranta
elementi
di
X
Larray
X
viene
prima
dimensionato
con
20
elementi
e
poi
ridimensionato
con
40
elementi.
Subito
dopo
il
ridimensionamento,
tutti
i
dati
precedenti
non
sono
pi
utilizzabili
(
come
se
venissero
cancellati).
E
possibile
anche
definire
un
array
di
array,
ad
esempio:
int
[
]
X;
int
[]
[]
U;
int
k;
U
=
new
int
[5][10];
X
=
U[3];
k
=
U[3][9];
La
variabile
U
rappresenta
una
matrice
di
5
righe
e
10
colonne.
Con
il
comando
X
=
U[3]
copiamo
la
4^
riga
(si
ricorda
che
lindice
parte
da
0)
di
U
in
X.
Con
il
comando
k
=
U[3][9]
copiamo
lelemento
della
4^
riga
e
della
10^
colonna
in
k.
Un
primo
esempio
di
utilizzo
degli
array,
l'abbiamo
gi
visto
nella
codifica
di
un
intero
relativo
in
16
bit
con
complemento
a
due
allorquando
abbiamo
utilizzato
un
array
per
memorizzare
le
cifre
binarie
mana
mano
che
venivano
prodotte
e
poi
stamparle
alla
fine
nell'ordine
giusto.
Come
secondo
esempio
di
utilizzo
degli
array,
scriviamo
un
programma
che
legge
una
stringa
e
per
ogni
carattere
di
essa,
determina
il
numero
di
occorrenze
(frequenza)
nella
stringa.
Ad
esempio,
se
la
stringa
cara
carla,
il
carattere
c
ha
frequenza
2,
a
4,
r
2,
l
1.
public static void main(String [] args){ Scanner reader = new Scanner([Link]); [Link]("Introduci il testo"); String testo = [Link](); int n = [Link](); // viene definito un array elencoCar di massimo n caratteri // in cui memorizzare tutti i possibili caratteri diversi di testo // nel caso peggiore ci saranno n caratteri diversi // ma in generale ce ne saranno di meno essendoci caratteri duplicati char [] elencoCar = new char [n]; // viene definito un array freqCar di massimo n interi // se elencoCar[5] vale c e freqCar[5] vale 3 // significa che il carattere c ha frequenza 3 int [] freqCar = new int [n]; // m indica il numero di caratteri differenti nel testo int m = 0; // inizialmente 0 caratteri diversi in testo for ( int i = 0; i < n; i++ ) { char carCorr = [Link](i); // carattere i-mo in testo // verifica se carCorr gi in elencoCar o va inserito boolean trovatoCar = false; for ( int j = 0; j < m && !trovatoCar; j++ ) if ( carCorr == elencoCar[j] ) { // carCorr in elencoCar, // quindi va incrementata la frequenza trovatoCar = true; freqCar[j]++; } if ( !trovatoCar ) { // carCorr non in elencoCar // carCorr va inserito in elencoCar con frequenza 1 elencoCar [m] = carCorr; freqCar[m]=1; m++; // va incrementato il numero di caratteri diversi } }; [Link]("Frequenze dei caratteri"); for ( int i = 0; i < m; i++ ) { [Link]("Carattere <" + elencoCar[i]+">: "); [Link](freqCar[i]); };
}
3
Si
noti
che
sono
utilizzati
due
array:
larray
di
caratteri
elencoCar
e
larray
di
interi
freqCar.
Entrambi
sono
dimensionati
con
un
numero
di
elementi
pari
alla
lunghezza
della
stringa
testo.
Tuttavia,
in
generale
non
tutti
questi
elementi
sono
utilizzati
in
quanto
i
due
array
contengono
tanti
elementi
quanti
sono
i
caratteri
differenti
di
testo.
Gli
elementi
dei
due
array
sono
costruiti
iterativamente,
attraverso
la
scansione
di
tutti
i
caratteri
di
testo:
per
ogni
carattere,
si
va
a
verificare
se
esso
gi
compreso
in
elencoCar
;
se
presente,
si
incrementa
di
1
la
relativa
frequenza;
altrimenti
si
aggiunge
il
carattere
a
elencoCar
e
si
pone
la
relativa
frequenza
a
1.
3.
Definizione
di
Metodi
Statici
Nello
scrivere
un
programma
opportuno
seguire
alcune
regole
di
buona
scrittura.
Una
di
queste
consiste
nel
suddividere
un
programma
in
unit
specializzate
a
svolgere
particolari
funzioni.
Tali
unit
di
programmazione
sono
chiamati
metodi
e
sono
definiti
in
maniera
analoga
al
metodo
main.
Essi
vengono
richiamati
dal
main
o
da
altri
metodi.
Alla
chiamata
del
metodo,
vanno
passati
tutti
i
parametri
indicati
nella
definizione
del
metodo.
Quando
ha
completato
la
sua
esecuzione,
il
metodo
o
non
restituisce
nulla
o
restituisce
un
valore
di
un
tipo
predefinito.
Come
primo
esempio
si
consideri
la
seguente
traccia
dellesercizio
2
dellappello
del
22/07/2011.
Traccia:
Si
scriva
un
metodo
Java
comprimi
che
riceve
in
ingresso
un
vettore
di
interi
V
e
restituisce
un
vettore
di
interi
C
che
ne
rappresenta
la
versione
compressa.
Il
processo
di
compressione
fa
corrispondere
ad
una
sequenza
ininterrotta
di
K
interi
N
nel
vettore
V,
la
sequenza
(K,N)
nel
vettore
C.
Ad
esempio,
se
V=[1,1,1,1,3,3,4,3,7,7,7,7,7]
allora
C=[4,1,
2,3,
1,4,
1,3,
5,7].
Soluzione:
Scriviamo
la
soluzione
come
un
metodo
statico
Java
di
nome
comprimi
che
riceve
in
ingresso
un
array
di
interi
V
(un
solo
parametro
formale)
e
restituisce
un
array
di
interi:
static int [ ] comprimi ( int [ ] V ) { int [ ] K = new int[[Link]*2]; // caso peggiore di elementi tutti // distinti int iK = 0; // indice del vettore K int iV = 0; // indice del vettore V while ( iV < [Link] ) { // scandiamo tutti gli elementi di V int iV1; boolean elemDiff=false; // indica se trovato un elemento differente for ( int j = iV+1; j < [Link] && !elemDiff; j++ ) if ( V[j] != V[iV] ) { iV1 = j; elemDiff = true; } if ( !elemDiff ) iV1 = [Link]; K[iK] = iV1 - iV; // viene memorizzato il numero di ripetizioni K[iK+1] = V[iV]; // viene memorizzato lelemento ripetuto iK += 2; // lindice di K viene incrementato di 2 iV = iV1; // la scansione di V riparte dallindice iV1 } int [ ] C = new int[iK] // dimensione effettiva di C for ( int i = 0; i < iK; i++ ) C[i] = K[i]; // viene ricopiato K in C return C; }
La
soluzione
per
lesame
tutta
qui.
Ma
se
si
vuole
provare
il
metodo
necessario
inserirlo
in
una
classe
insieme
a
un
metodo
main
(ed
eventuali
ulteriori
altri
metodi):
Class Compressione { static public void main (String [] args) { int [] V= leggiVettore (); int [] C = comprimi(V); stampaVettore(C); } static int [ ] comprimi ( int [ ] V ) { // vedi codice precedente } static int [] leggiVettore () { Scanner reader = new Scanner([Link]); [Link]("Numero di elementi del vettore V?"); int n = [Link](); int [] V = new int[n]; for ( int i = 0; i < n; i++ ) { [Link]("V["+i+"]?"); V[i] = [Link](); } return V; } static void stampaVettore ( int [] C) { [Link]("Compressione di V"); [Link]("[ "); // senza salto linea for ( int i = 0; i < [Link]; i+=2 ) { [Link]( C[i]+":"+C[i+1]); // senza salto linea if ( i < [Link]-2 ) [Link]( ", "); // senza salto linea } [Link](" ]"); // salto linea [Link]("bye"); } }
Lutilit
di
utilizzare
metodi
piuttosto
che
inserire
direttamente
il
codice
nel
metodo
main
essenzialmente
quella
di
evitare
di
distrarsi
con
limplementazione
di
dettagli
secondari
(tali
sono
da
considerare
due
semplici
metodi
di
lettura
e
scrittura
di
un
array),
rimandandola
a
un
secondo
momento
o,
ancora
meglio,
cercando
di
utilizzare
metodi
gi
scritti
(eventualmente
opportunamente
modificati).
Come
un
secondo
esempio
si
consideri
il
seguente
metodo
per
il
calcolo
del
fattoriale
di
un
numero
n,
calcolato
come
12(n-1)
n:
static int fattoriale ( int n ) { if ( n < 2 ) return 1; else { int ris = 2; for ( int i =3; i <= n; i++ ) ris = ris*i; return ris; } }
Esso
restituisce
un
intero
e
ha
un
parametro
formale
(la
variabile
n)
di
tipo
intero.
Per
richiamare
il
metodo
bisogna
scrivere
il
nome
del
metodo
(nellesempio
fattoriale
e
passare
un
parametro
attuale
di
tipo
intero,
cio
un
valore
effettivo
per
il
quale
calcolare
il
fattoriale.
Il
metodo
restituisce
un
intero.
Ad
esempio
il
metodo
pu
essere
richiamato
con
listruzione:
int R = fattoriale(4);
Dopo
la
chiamata,
la
variabile
di
R
conterr
24,
cio
il
fattoriale
di
4.
Il
metodo
restituisce
1
se
gli
viene
passato
un
numero
negativo.
Volendo
eliminare
questo
inconveniente,
potremmo
interpretare
che
fattoriale(-n)
con
n
negativo,
corrisponda
a
fattoriale(n)
e
riscrivere
il
metodo
come:
static int fattoriale ( int n ) { int segno = 1; if ( n < 0) { n = -n; segno =-1; } if ( n < 2 ) return 1*segno; else { int ris = 2; for ( int i =3; i <= n; i++ ) ris = ris*i; return ris * segno; } }
Ora
presentiamo
il
metodo
fibonacci
si
ricorda
che
fibonacci(0)
vale
0,
fibonacci(1)
vale
1
e
per
ogni
i
>
1,
fibonacci(i)
=
fibonacci(i-1)+fibonacci(i-2).
La
sequenza
dei
primi
numeri
di
Fibonacci
:
0,
1,
1,
2,
3,
5,
8,
13,
21,
34,
55,
89,
144,
233,
377,
610,
987,
1597,
2584,
4181,
6765.
static int fibonacci ( int n ) { if (n < 2 ) return n; else { int F1 = 1, F2=0, F=1; for (int i= 3; i <= n; i++ ){ F2=F1; F1=F; F=F1+F2; } return F; } } // fine metodo fibonacci
Di
seguito
presentiamo
una
classe
con
3
metodi:
il
metodo
main,
lanciato
automaticamente
con
lavvio
della
classe,
che
a
sua
volta
richiama
i
metodi
fattoriale
e
fibonacci.
public class CalcolaFF { public static void main(String[] args) { Scanner reader = new Scanner([Link]); // prima lettura del comando di interazione [Link]("Inserisci A per calcolare fattoriale, [Link]("B per fibonacci e C per finire [A/B/C]?"); char comando = [Link]([Link]()); // sessione interattiva while ( comando == 'A' || comando == 'B' ) { [Link]("Inserisci n "); int n = [Link](); int ris; if (comando =='A') { [Link]("Fattoriale = "); ris = fattoriale(n); [Link](ris); // si pu anche scrivere direttamente solo una istruzione // [Link](fattoriale(n));
} else { [Link]("Fibonacci = "); ris = fibonacci(n); [Link](ris); // si pu anche scrivere direttamente solo una istruzione // [Link](fibonacci(n)); } // successiva lettura del comando di interazione [Link]("Inserisci A per calcolare fattoriale, [Link]("B per fibonacci e C per finire [A/B/C]?"); comando = [Link]([Link]()); } [Link]("bye"); } // fine metodo main // METODO FATTORIALE static int fattoriale ( int n ) { // vedi codice precedente } // fine metodo fattoriale // METODO FIBONACCI static int fibonacci ( int n ) { // vedi codice precedente } } // fine metodo fibonacci } // fine classe CalcolaFF
Come
secondo
esempio
di
utilizzo
di
metodi,
consideriamo
nuovamente
il
programma
che
calcola
le
frequenze
di
caratteri
in
un
testo.
Vogliamo
aggiungere
una
sessione
interattiva
che
permetta
allutente
di
chiedere
se
vuole
conoscere
la
frequenza
di
un
dato
carattere
o
quali
sono
i
caratteri
con
frequenza
maggiore
o
uguale
ad
un
dato
valore.
Piuttosto
che
estendere
il
programma
e
renderlo
meno
leggibile
(un
metodo
non
dovrebbe
mai
essere
troppo
lungo),
possiamo
definire
due
appositi
metodi:
// METODO trovaFrequenzaCar static void trovaFrequenzaCar ( Scanner reader, char[] C, int [] F, int m) { // dato un carattere, viene verificato se presente nell'elenco C // se presente ne viene stampata la frequenza [Link]("Carattere?"); char car= [Link](); boolean trovato = false; for (int i =0; i < m && !trovato; i++ ) { if ( C[i] == car ) { trovato = true; [Link]("Carattere <" + C[i]+">: "+F[i]); } }; if (!trovato ) [Link]("Carattere <" + car+"> non presente."); } // fine metodo trovaFrequenzaCar // METODO trovaCarFrequenti static void trovaCarFrequenti (Scanner reader, char[] C, int [] F, int m) { // data una frequenza minima fMin, vengono individuati tutti i caratteri // nell'elenco che abbiano una frequenza di almeno fMin [Link]("Frequenza minima?"); int fMin = [Link](); for (int i =0; i < m; i++ )
if ( F[i] >= fMin ) [Link]("Carattere <" + C[i]+">: "+F[i]); } fine metodo trovaCarFrequenti
Si
noti
che
entrambi
i
metodi
fanno
utilizzano
loggetto
reader
definito
nel
main
e
passato
come
parametro.
Pertanto
abbiamo
due
possibilit:
o
passarlo
come
parametro
(ma
non
labbiamo
inserito
tra
i
parametri
formali)
o
definire
reader
come
variabile
statica.
Vediamo
come
realizzare
questultima
soluzione:
public class ContaCaratteri { Scanner reader = new Scanner([Link]); // variabile statica inizializzata public static void main(String [] args){ Scanner reader = new Scanner([Link]); [Link]("Introduci il testo"); String testo = [Link](); // . . . // vedi codice precedente // . . . [Link]("Frequenze dei caratteri"); for ( int i = 0; i < m; i++ ) { [Link]("Carattere <" + elencoCar[i]+">: "); [Link](freqCar[i]); }; // parte aggiuntiva char comando; do { // sessione interattiva con l'utilizzatore [Link]("Frequenza di un Carattere o); [Link](" Caratteri pi Frequenti o Fine? (A/B/C)"); comando = [Link]([Link]()); if ( comando == 'A' ) trovaFrequenzaCar(reader,elencoCar,freqCar,m); else if ( comando == 'B' ) trovaCarFrequenti (reader,elencoCar,freqCar,m); } while ( comando != 'C' ); [Link]("Bye");
} // fine main
// inserire qui le definizioni di
trovaFrequenzaCar e trovaCarFrequenti
}
//
fine
classe
ContaCaratteri
Si
noti
che
lestensione
del
metodo
main
limitata
a
poche
linee
di
codice
e
le
due
nuove
funzioni
aggiunte
sono
rese
astratte
attraverso
il
ricorso
a
due
appositi
metodi.
Lutilizzo
di
astrazione
ci
permette
di
scrivere
un
programma
senza
dover
fornire
tutti
i
dettagli
implementativi.
Ovviamente,
alla
fine
essi
dovranno
essere
forniti
cio
i
metodi
dovranno
essere
scritti.
Ma
rinviare
la
scrittura
risulta
estremamente
utile
per
un
approccio
sistematico
in
cui
si
parte
dai
concetti
generali
per
poi
passare
a
concetti
pi
specifici
e
dettagliati:
si
parla
di
stepwise
refinement
(raffinamento
dei
concetti
a
passi
successivi).
Addirittura
il
main
nellesempio
potrebbe
essere
ridotto
ulteriormente
spostando
una
parte
del
codice
in
un
apposito
metodo
CalcolaFrequenzaCar:
public static void main(String [] args){ Scanner reader = new Scanner([Link]); [Link]("Introduci il testo"); String testo = [Link](); int n = [Link](); char [] elencoCar = new char [n]; int [] freqCar = new int [n]; int m = calcolaFrequenzaCar(testo, elencoCar, freqCar); [Link]("Frequenze dei caratteri");
Il
metodo
CalcolaFrequenzaCar
definito
nel
seguente
modo:
for ( int i = 0; i < m; i++ ) { [Link]("Carattere <" + elencoCar[i]+">: "); [Link](freqCar[i]); }; char comando; do { // sessione interattiva con l'utilizzatore [Link]("Frequenza di un Carattere o); [Link](" Caratteri pi Frequenti o Fine? (A/B/C)"); comando = [Link]([Link]()); if ( comando == 'A' ) trovaFrequenzaCar(reader,elencoCar,freqCar,m); else if ( comando == 'B' ) trovaCarFrequenti (reader,elencoCar,freqCar,m); } while ( comando != 'C' ); [Link]("Bye"); } // fine main
static int calcolaFrequenzaCar ( String testo, char [] elencoCar, int [] freqCar ) { int n = [Link](); int m = 0; for ( int i = 0; i < n; i++ ) { char carCorr = [Link](i); boolean trovatoCar = false; for ( int j = 0; j < m && !trovatoCar; j++ ) if ( carCorr == elencoCar[j] ) { trovatoCar = true; freqCar[j]++; } if ( !trovatoCar ) { elencoCar [m] = carCorr; freqCar[m]=1; m++; } }; return m, }
Come
ulteriore
esempio
torniamo
alla
traccia
del
22/07/2011.
La
soluzione
pu
essere
scritta
ne
seguente
moto
utilizzando
un
metodo
primoDiverso
che
riceve
come
parametri
formali
un
array
di
interi
W
e
un
intero
k
e
restituisce
un
intero
che
rappresenta
lindice
del
primo
elemento
di
W
successivo
a
W[k]
che
diverso
da
W[k].
static int [ ] comprimi ( int [ ] V ) { int [ ] K = new int[[Link]*2] int iK = 0, iV = 0; while ( iV < [Link] ){ int iV1 = primoDiverso(V,iV); K[iK] = iV1 iV; K[iK+1] = V[iV]; iK += 2; iV = iV1; } int [ ] C = new int[iK] // dimensione effettiva di C for ( int i = 0; i < iK; i++ ) C[i] = K[i]; return C; }
static int primoDiverso( int [ ] W, int k ) { for ( int i = k+1; i < [Link]; i++ ) if ( W[k] != W[i] ) return i; return [Link]; }
Il
vantaggio
delluso
del
metodo
solo
nella
chiarezza
della
lettura
del
programma
e
nella
maggiore
facilit
di
scrittura
di
esso,
derivante
dal
meccanismo
potente
dellastrazione:
possiamo
scrivere
il
programma
assumendo
che
esista
un
metodo
che
svolga
una
certa
funzione
che
per
il
momento
non
vogliamo
implementare.
Limplementazione
viene
rimandata
a
un
secondo
momento
o
addirittura
evitata
se
si
riesce
ad
affidare
il
compito
ad
altri
o
a
trovarla
gi
disponibile.
Per
concludere,
sottolineiamo
che
i
metodi
introdotti
sono
di
tipo
statico
nel
senso
che
sono
metodi
della
classe
e
non
di
oggetti
della
classe.
E
possibile
definire
anche
metodi
non
statici,
ma
essi
non
saranno
trattati
nel
corso,
anche
se
ne
saranno
utilizzati
alcuni
in
effetti
i
metodi
di
lettura
associati
a
reader
sono
metodi
non
statici.
3.
Definizione
di
Variabili
Statiche
Quando
un
metodo
richiama
un
altro
metodo,
deve
condividere
con
esso
alcune
variabili
sulle
quali
il
metodo
chiamato
deve
effettuare
i
suoi
calcoli.
Il
modo
pi
utilizzato
quello
di
passare
tali
variabili
come
parametri.
Ma
anche
possibile
definire
allinterno
della
classe
alcune
variabili
statiche
che
sono
visibili
a
tutti
i
metodi
della
classe.
Ad
esempio
la
variabile
n
su
cui
va
effettuato
il
calcolo
del
fattoriale
o
del
Fibonacci,
invece
di
passare
tale
valore
come
parametro
attuale,
n
pu
essere
definita
come
variabile
statica
nel
seguente
modo:
public class CalcolaFF { static int n; // variabile statica public static void main(String[] args) { // prima lettura del comando di interazione [Link]("Inserisci A per calcolare fattoriale, [Link]("B per fibonacci e C per finire [A/B/C]?"); char comando = [Link]([Link]()); // sessione interattiva while ( comando == 'A' || comando == 'B' ) { [Link]("Inserisci n "); n = [Link](); // la variabile n gi definita if (comando =='A') { [Link]("Fattoriale = "); // a fattoriale non viene passato alcun parametro attuale [Link](fattoriale()); } else { [Link]("Fibonacci = "); // a fibonacci non viene passato alcun parametro attuale [Link](fibonacci()); } // successiva lettura del comando di interazione [Link]("Inserisci A per calcolare fattoriale, [Link]("B per fibonacci e C per finire [A/B/C]?"); comando = [Link]([Link]()); } [Link]("bye"); } // fine metodo main
10
// METODO FATTORIALE static int fattoriale ( ) { // nessun parametro formale if (n < 2 ) return 1; else { int ris = 2; for (int i= 3; i <= n; i++ ) ris = ris*i; return ris; } } // fine metodo fattoriale // METODO FIBONACCI static int fibonacci ( ) { // nessun parametro formale if (n < 2 ) return n; else { int F1 = 1, F2=0, F=1; for (int i= 3; i <= n; i++ ){ F2=F1; F1=F; F=F1+F2; } return F; } } // fine metodo fibonacci } // fine classe CalcolaFF
Ritornando
al
programma
sulla
frequenza
dei
caratteri
in
una
stringa,
possiamo
evitare
di
passare
i
vari
parametri
alle
funzioni
richiamate
definendole
come
statica
nel
seguente
modo:
public class ContaCaratteri { Scanner reader = new Scanner([Link]); // variabile statica inizializzata static String testo; static char[] elencoChar; statica int [] freqCar public static void main(String [] args){ [Link]("Introduci il testo"); testo = [Link](); // gi definito int n = [Link](); elencoCar = new char [n]; // gi definito freqCar = new int [n]; // gi definito int m = calcolaFrequenzaCar(); // nessun parametro // utilizzate variabili statiche [Link]("Frequenze dei caratteri"); for ( int i = 0; i < m; i++ ) { [Link]("Carattere <" + elencoCar[i]+">: "); [Link](freqCar[i]); }; char comando; do { // sessione interattiva con l'utilizzatore [Link]("Frequenza di un Carattere o); [Link](" Caratteri pi Frequenti o Fine? (A/B/C)"); comando = [Link]([Link]()); if ( comando == 'A' ) trovaFrequenzaCar(m); else if ( comando == 'B' ) trovaCarFrequenti (m);
11
} while ( comando != 'C' ); [Link]("Bye"); } // fine main
// METODO calcolaFrequenzaCar senza parametri formali static int calcolaFrequenzaCar ( ) { } // fine metodo calcolaFrequenzaCar // METODO trovaFrequenzaCar con un solo parametro formale static void trovaFrequenzaCar ( int m ) { // istruzioni non modificate // ... } // fine metodo trovaFrequenzaCar // METODO trovaCarFrequenti con un solo parametro formale static void trovaCarFrequenti ( int m ) { // istruzioni non modificate // ... } fine metodo trovaCarFrequenti
}
//
fine
classe
ContaCaratteri
Non
esistono
regole
precise
se
utilizzare
parametri
o
variabili
statiche.
Unindicazione
di
massima
che
lutilizzo
di
parametri
rende
il
metodo
pi
leggibile
(abbiamo
sottomano
tutte
le
variabili
utilizzate
nel
metodo
senza
dover
consultare
le
variabili
statiche
della
classe)
e
autosufficiente
(il
metodo
pu
essere
utilizzato
senza
dover
riportarsi
le
variabili
statiche).
Il
suggerimento
quindi
di
utilizzare
i
parametri
formali
il
pi
possibile.
4.
Ulteriori
Esercizi
Una
palindroma
una
stringa
che
pu
essere
letta
in
entrambe
le
direzioni,
ad
esempio
otto
oppure
ai
lati
dItalia.
In
effetti
questultima
stringa
una
palindroma
solo
se
si
trascurano
gli
spazi
e
i
caratteri
differenti
da
lettere
quale
lapice.
Se
si
escludo
gli
spazi
anche
questa
una
palindroma:
In
girum
imus
nocte
et
consumimur
igni.
Provate
a
scrivere
un
metodo
che
riceve
una
stringa
e
risponde
true
se
una
palindroma
considerando
solo
le
lettere
e
saltando
tutti
gli
altri
caratteri
compresi
gli
spazi.
Un
altro
esercizio
il
secondo
dellappello
del
5/0/2011:
Traccia:
Si
scriva
un
metodo
Java
verificaSottoVettori
che
riceve
in
ingresso
due
vettori
di
interi
V
e
W,
ognuno
dei
quali
contiene
valori
distinti,
restituisce
true
se
e
solo
se
gli
elementi
di
W
sono
una
sotto- sequenza
inversa
degli
elementi
di
V,
cio
gli
elementi
di
W
sono
tutti
contenuti
in
V
(anche
se
ne
possono
mancare
alcuni
di
essi)
e
sono
riportati
nella
stessa
sequenza
inversa
in
cui
occorrono
nel
vettore
V.
Ad
esempio,
se
V=[5,6,1,4,9,2,8,7]
e
W=[7,4,6,5]
allora
il
metodo
restituisce
true
mentre
con
W
=
[5,6,4,7]
o
W
=
[4,7,6,5]
il
metodo
restituisce
false.
Soluzione:
static boolean verificaSottoVettori ( int [ ] V, int [ ] W ) { if ( !tuttiDistinti(V) || !tuttiDistinti(W) ) return false; int pos = 0; for ( int i = [Link]-1; i >=0; i-- ) { pos = compresoDaPos(W[i],V,pos)+1; if ( pos == 0 ) return false; // oppure return pos != 0 } return true;
12
} static int compresoDaPos( int X, int [ ] V, int k ) { for ( int i = k; i < [Link]; i++ ) if ( X == V[i] ) return i; return -1; } static boolean tuttiDistinti( int [ ] K ) { for ( int i = 0; i < [Link]-1; i++ ) if ( compresoDaPos(K[i],K,i+1) >= 0 ) return false; return true; }
E
interessante
notare
che
lo
stesso
metodo
compresoDaPos
sia
utilizzato
da
una
parte
per
verificare
la
propriet
di
sottostringa
definita
dalla
traccia
e
dallaltra
per
verificare
preliminarmente
che
entrambi
i
vettori
contengano
tutti
elementi
distinti.
13