Partiamo subito da un caso specifico: poniamo di voler costruire un programma per calcolare la media o il massimo di un elenco di valori contenuti in una casella di testo, il risultato sarebbe un programma come quello qui sotto.
public class MediaMaggiorePlain extends Application{
TextField valori = new TextField();
Label risposta = new Label();
@Override
void start(Stage finestra){
Label e_elenco = new Label("elenco: ");
Label e_risposta = new Label("risposta: ");
Button p_media = new Button("media");
Button p_massimo = new Button("massimo");
GridPane griglia = new GridPane();
griglia.add(e_elenco, 0, 0);
griglia.add(valori, 1, 0, 2, 1);
griglia.add(p_media, 1, 1);
griglia.add(p_massimo, 2, 1);
griglia.add(e_risposta, 0, 2);
griglia.add(risposta, 1, 2);
Scene scena = new Scene(griglia);
finestra.setScene(scena);
finestra.setTitle("media & massimo");
finestra.show();
p_media.setOnAction(evento->calcolaMedia());
p_massimo.setOnAction(evento->trovaMassimo());
}
void trovaMassimo(){
String testi[] = valori.getText().split(" ");
int numeri[] = new int[testi.length];
int massimo;
for (int i = 0; i < testi.length; i++) {
numeri[i] = Integer.parseInt(testi[i]);
}
massimo = numeri[0];
for (int i = 1; i < numeri.length; i++) {
if(numeri[i]>massimo){
massimo = numeri[i];
}
}
risposta.setText(""+massimo);
}
void calcolaMedia(){
String testi[] = valori.getText().split(" ");
int numeri[] = new int[testi.length];
int somma=0,media;
for (int i = 0; i < testi.length; i++) {
numeri[i] = Integer.parseInt(testi[i]);
}
for (int i = 0; i < numeri.length; i++) {
somma += numeri[i];
}
media = somma/numeri.length;
risposta.setText(""+media);
}
public static void main(String args[]){
launch(args);
}
}
Una parte del codice è evidenziata per far vedere che ci sono due segmenti che si ripetono praticamente identici, questo fatto comporta una serie di problemi:
- dobbiamo lavorare il doppio per scrivere la stessa cosa (anche facendo copia/incolla comunque bisogna lavorar di più)
- se potessimo scrivere quelle parti una sola volta il programma verrebbe più corto
- se ci accorgiamo di un errore potremmo finire con il correggerlo in un punto ma non in un altro.
Fortunatamente in tutti i linguaggi attuali c'è una soluzione: scrivere una funzione
(che nei linguaggi ad oggetti si chiama "metodo") che fa il lavoro comune.
Questa funzione farà un lavoro ben determinato: leggerà i dati dalla casella valori
e avrà come risultato un vettore di interi, in pratica:
int[] leggiDati(){
String testi[] = valori.getText().split(" ");
int numeri[] = new int[testi.length];
for (int i = 0; i < testi.length; i++) {
numeri[i] = Integer.parseInt(testi[i]);
}
return numeri;
}
Proviamo a vedere cosa c'è scritto nel pezzo di codice precedente. Dalla prima riga, che è la dichiarazione della funzione, si possono ricavare alcune informazioni: stiamo dichiarando una funzione (un tipo seguito da un nome e da una parentesi tonda), la funzione si chiama "leggiDati" (il nome prima della tonda aperta) e da come risultato un vetore di interi (il tipo prima del nome).
Tutto il resto dovrebbe risultare familiare tranne l'ultima riga: return
è una istruzione che indica cosa va restituito a chi chiama (sarebbe a dire usa) la funzione.
Usare una funzione non è molto difficile, è esattamente come usare il metodo getText()
di un Button
, niente di più, quel che cambia è che possiamo direttamente
scrivere il nome della funzione perché si trova nella nostra classe
(non serve scrivere il nome di un oggetto seguito da ".")
void trovaMassimo(){
int numeri[] = leggiDati();
int massimo;
massimo = numeri[0];
for (int i = 1; i < numeri.length; i++) {
if(numeri[i]>massimo){
massimo = numeri[i];
}
}
risposta.setText(""+massimo);
}
Passaggio di parametri
La funzione vista in precedenza calcola un risultato ma non ha nessun valore in input su cui lavorare: è possibile però fornirglieli utilizzando il passaggio di parametri.
Il passaggio dei parametri (che sono delle variabili) si fa indicando tra le parentesi tonde che seguono il nome della funzione una sequenza di dichiarazioni, proviamo a vedere ad esempio una funzione che calcola il totale di una spesa:
// definizione
double calcolaPrezzoTotale(int quantita, double prezzoUnitario) {
double totale;
totale = quantita * prezzoUnitario;
return totale;
}
// uso (chiamata)
double t;
int q = 5;
double pu = 3.2;
t = calcolaPrezzoTotale(q , pu);
I parametri presenti nella definizione si chiamano parametri formali mentre quelli nella chiamata vengono detti parametri attuali.
In questo caso i parametri sono due: quantita di tipo int e prezzoUnitario di tipo double. L'ordine dei parametri è importante così come il tipo: quando chiamo la funzione i parametri forniti (parametri attuali) devono combaciare con quelli attesi (parametri formali) sia nel tipo che nell'ordine.
In Java il passaggio dei parametri (dalla funzione che chiama a quella che viene chiamata) avviene per valore: questo vuol dire che nel parametro formale viene copiato il valore passato come parametro attuale.
static void incrementaNumero(int i){
i++;
}
public static void main(String args[]){
int x=1;
System.out.println(x); // stampa "1"
incrementaNumero(x);
System.out.println(x); // stampa sempre "1"
}
In caso i parametri siano di tipo riferimento quello che viene passato è una copia dell'indirizzo di memoria del parametro attuale: questo significa che se la funzione apporta delle modifiche al parametro formale queste queste saranno in pratica applicate anche al parametro attuale, sarà molto più chiaro con un esempio:
static void incrementaElementi(int m[]){
for(int i=0; i<m.length; i++){
m[i]++;
}
}
public static void main(String args[]){
int [] v = new int[2];
v[0] = 4;
v[1] = 2;
int s;
s = v[0]+v[1];
System.out.println(s); // stampa "6"
incrementaElementi(v);
s = v[0]+v[1];
System.out.println(s); // stampa "8"
}
Valore restituito
Ogni funzione può o meno restituire un valore a chi l'ha chiamata: il tipo
del valore restituito viene dichiarato prima del nome della funzione e in
caso che non ci sia un valore da restituire questo viene indicato con
la parola chiave void
al posto del tipo.
All'interno della funzione la parola chiave return
serve per terminare la funzione e restituire a chi l'ha chiamata il valore
dell'espressione che la segue (es: return a*7;
).
Ovviamente il valore restituito deve
essere del tipo dichiarato prima del nome della funzione, in caso di una
funzione void
il return non è necessario (ma se si vuole
usarlo non deve essere eseguito da nessuna espressione).
Adesso possiamo riassumere: una dichiarazione di funzione è un tipo (oppure void) seguito da un nome (stesse regole dei nomi di variabili) e da una lista di parametri tra parentesi tonde (anche se non ce ne è nessuno va bene) e un blocco di codice racchiuso tra graffe.
Come funziona
Usiamo l'esempio qui sotto per definire con precisione come funzionano le cose: il programma qui sotto inizierà la sua esecuzione dalla fuzione chiamata main (alla riga 7).
La riga uno contiene la dichiarazione della funzione: il suo nome è somma, restituirà un
numero intero e ha due parametri formali: n
e q
che assumeranno un valore
quando la funzione verrà chiamata (cioè utilizzata). La funzione potrebbe non ritornare nessun valore
e in quel caso il tipo ritornato sarebbe void
.
Le righe 2 e 3 servono a calcolare la somma mentre la 4 è quella che dice che la funzione deve
terminare e restituire a chi l'ha chiamata (ad esempio alla riga 9) il valore contenuto nella variabile s
.
return
è seguita da una espressione (anche una semplice variabile è una espressione)
che deve essere dello stesso tipo ritornato dalla funzione (nel caso di funzioni void
non avrà alcuna espressione), quando si esegue un return
la funzione termina.
Il programma inizia la sua esecuzione alla riga 7, la 8 è una semplice dichiarazione di variabile
mentre la 9 è quella che ci interessa: quando il programma arriva qui sospende la funzione
main
e passa a somma
copiando i parametri attuali "3" e "9" in quelli
formali "n" e "q" della funzione. A questo punto viene eseguita la riga 3, s = "3 + 9"
e poi alla 4 la funzione termina restituendo 12.
Quindi l'esecuzione torna alla riga 10 dove avevamo sospeso, il valore restituito dalla funzione somma viene copiato nella variabile a che quindi a questo punto conterrà 12.
funzioni e variabili
codice
1int somma(int n, int q){
2 int s;
3 s = n + q;
4 return s;
5}
6
7void main()
8 int a;
9 int b;
10 a = somma(3, 9);
11 b = a*2;
12}
Continua esempio
Tornando all'esempio iniziale potrebbe succedere che l'elenco dei numeri
si trovi in una casella oppure in un'altra, in questo caso la nostra funzione
leggiDati()
non va più bene
perché legge sempre dalla stessa casella: potrebbe far comodo
modificare la funzione in modo che prenda come input un testo
e restituisca un vettore di numeri (esattamente come prima).
Chiamiamo questa nuova funzione daStringaAVettore.
int[] daStringaAVettore(String testo){
String testi[] = testo.split(" ");
int numeri[] = new int[testi.length];
for (int i = 0; i < testi.length; i++) {
numeri[i] = Integer.parseInt(testi[i]);
}
return numeri;
}
In questo caso per usare la funzione sarà necessario fare un passaggio in più perché la funzione vuole un dato in input (un testo) quindi leggerò un testo e lo passerò come parametro:
String testoCasella = valori.getText(); int numeri[] = daStringaAVettore(testoCasella);
Nel nostro caso:
- int[]
- (la parte prima del nome) la funzione restituirà (a chi l'ha chiamata) un vettore di interi
- daStringaAVettore
- il nome della funzione (viene usato ad esempio per chiamarla)
- String testo
- la funzione richiede un parametro formale
di tipo String che all'interno del corpo della funzione
sarà riferito con la variabile
testo
Riguardo public int calcolaNumero(int a, int b)
quali delle seguenti affermazioni sono vere?
public class Incrementa {
static int incrementa(int x){
x++;
return x;
}
public static void main(String[] args) {
int a = 10;
int b = incrementa(a);
System.out.println(a+", "+b);
}
}
cosa comparirà come risultato del programma sopra?
11, 11no, la variabile a non viene modificata dalla funzione perché x contiene una copia del valore di a, non è la stessa variabile 10, 11 giusto, x viene incrementata ma è una copia di a che quindi non viene mai modificata nel programma