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
Rispetto all'esempio precedente potrebbe succedere che l'elenco dei numeri si trovi in una casella oppure in un'altra, in questo caso la nostra funzione non va più bene perché legge sempre dalla stessa casella: potrebbe in questo caso far comodo modificare la nostra funzione in modo che prenda come input un testo (String) e restituisca un vettore di numeri (esattamente come prima).
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 fornirò come input (in realtà si dice che "lo passerò come parametro"):
String testoCasella = valori.getText();
int numeri[] = daStringaAVettore(testoCasella);
Adesso possiamo riassumere: una dichiarazione di funzione è un tipo 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, in pratica:
- daStringaAVettore
- il nome della funzione (viene usato ad esempio per chiamarla)
- int[]
- (la parte prima del nome) la funzione restituirà (a chi l'ha chiamata) un vettore di interi
- String testo
- la funzione richiede un parametro (si chiamano parametri formali)
di tipo String che all'interno della funzione
sarà riferito come
testo
(cioè all'interno della funzione potremmo usare una variabile chiamatatesto
Riguardo public int calcolaNumero(int a, int b)
quali delle seguenti affermazioni sono vere?
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}
Passaggio per valore
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. Sembra complicato? Vediamo con un esempio.
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); } }