Lezione 6 · Canale 2 · martedì 6 ottobre 2026

Metodi statici, parametri e array in Java

Progettazione del Software

Riassunto

I metodi statici si invocano tramite il nome della classe e non hanno un oggetto implicito. L'overloading consente di definire metodi con lo stesso nome ma parametri diversi. I parametri sono passati per valore: per i tipi primitivi viene copiato il valore; per gli oggetti viene copiato il riferimento. Le variabili locali e i parametri occupano il record di attivazione del metodo nello stack. La ricorsione permette a un metodo di richiamare sé stesso; il fattoriale è un esempio classico. Le variabili statiche sono associate alla classe e condivise; il modificatore final le rende costanti. Gli array Java sono oggetti allocati nell'heap: una variabile array contiene un riferimento. La lunghezza è fissata al momento della creazione e gli indici vanno da zero a length - 1. La classe Arrays offre metodi per copia, ordinamento e rappresentazione testuale. Gli array multidimensionali sono array di array: le righe di una matrice possono avere lunghezze diverse. Passare un array a un metodo può causare side effect, perché il metodo modifica lo stesso oggetto nell'heap.

Concetti chiave

  • Metodo statico — Metodo invocabile attraverso il nome della classe, senza un oggetto invocato implicito.
  • Oggetto invocato — Oggetto sul quale si chiama un metodo di istanza; il metodo può operare sullo stato dell'oggetto.
  • Modulo funzionale — Classe usata per offrire operazioni e valori statici che non si vogliono associare a una specifica istanza.
  • Overloading — Definizione, nella stessa classe, di metodi con lo stesso nome e liste di parametri diverse per numero, tipo o ordine.
  • Parametro formale — Variabile dichiarata nella definizione del metodo e inizializzata con il valore ricevuto alla chiamata.
  • Parametro attuale — Valore o espressione fornito dal chiamante per un parametro formale.
  • Scope — Ambito nel quale una variabile è visibile; una variabile locale è visibile nel metodo in cui è dichiarata.
  • Casting — Conversione esplicita da un tipo a un altro compatibile; la conversione da double a int tronca la parte decimale.
  • Ricorsione — Tecnica in cui un metodo richiama sé stesso; le chiamate successive occupano record di attivazione distinti nello stack.
  • Variabile statica — Variabile associata alla classe e condivisa, anziché appartenere a una singola istanza o chiamata del metodo.
  • Array — Oggetto che contiene elementi di un tipo determinato, accessibili per indice intero; la sua lunghezza è fissata al momento della creazione.
  • Ciclo for migliorato — Forma di for che attraversa direttamente gli elementi di un array, senza usare esplicitamente gli indici.

Sviluppo

Metodo statico e oggetto invocato

Una chiamata a metodo statico usa il nome della classe seguito dal nome del metodo e dagli argomenti, per esempio Classe.metodo(parametri). A differenza di un metodo di istanza, non ha un oggetto invocato implicito: nella chiamata stringa1.concat(stringa2), invece, stringa1 è l'oggetto sul quale opera il metodo e stringa2 è un parametro.

Un metodo statico non ha un riferimento implicito all'istanza, ma può comunque lavorare con oggetti e accedere all'heap tramite riferimenti ricevuti o creati al suo interno.

Moduli funzionali e funzioni matematiche

Un modulo funzionale raccoglie operazioni che non è naturale attribuire a un oggetto specifico. La classe non è un oggetto in memoria: è una definizione che offre metodi statici. Un esempio è Math, che mette a disposizione operazioni matematiche come Math.cos(alfa) e Math.sqrt(valore). Il risultato del coseno è un nuovo valore: non modifica il numero passato come argomento.

Questo è anche una scelta di progettazione: alcune funzionalità si considerano offerte dal sistema o da una classe di utilità, anziché da un singolo oggetto.

Generazione casuale e casting

Math.random() restituisce un valore di tipo double compreso tra 0 incluso e 1 escluso. Per ottenere un intero casuale tra 1 e 6 si può moltiplicare il valore per 6, convertirlo a int e poi sommare 1:

int dado = (int) (Math.random() * 6) + 1;

In Java la conversione da double a int si scrive indicando il tipo tra parentesi tonde. Questa conversione tronca la parte decimale, quindi, dopo la moltiplicazione, produce un valore tra 0 e 5; la somma di 1 sposta l'intervallo tra 1 e 6.

Definizione dei metodi e valore di ritorno

La forma generale di un metodo statico comprende modificatori, tipo restituito, nome e parametri. Per esempio:

public static int somma(int a, int b) {
    return a + b;
}

La parola chiave return restituisce il risultato al chiamante. Il chiamante può assegnarlo a una variabile o usarlo in un'espressione; se lo ignora, il valore non viene conservato dal chiamante. Un metodo può restituire un tipo primitivo, un riferimento a un oggetto oppure void. Un metodo void non deve restituire un valore.

Overloading

L'overloading permette di definire nella stessa classe più metodi con lo stesso nome, purché abbiano liste di parametri diverse per numero, tipo o ordine. Cambiare soltanto il tipo restituito non basta a distinguere due metodi.

Un esempio è una famiglia di metodi per stampare saluti: una versione senza parametri può stampare un saluto generico, mentre una versione che riceve un nome può personalizzarlo. Nella stessa famiglia si possono prevedere ulteriori varianti, per esempio una che riceve due nomi. Riunire varianti della stessa funzionalità sotto un nome coerente rende il codice più leggibile e ne comunica meglio l'intento.

Scope e record di attivazione

Una variabile dichiarata dentro un metodo è visibile nel relativo ambito e occupa spazio nel record di attivazione del metodo sullo stack. Anche i parametri sono variabili di quel record: quando il metodo viene chiamato, i parametri formali vengono inizializzati con i valori dei parametri attuali.

Per esempio, se un metodo è dichiarato con il parametro formale String nome e viene chiamato passando "Mario", nel record di attivazione la variabile nome riceve quel valore.

Passaggio dei parametri e side effect

In Java gli argomenti sono passati per valore. Per un tipo primitivo viene copiato il valore; per un oggetto viene copiato il valore del riferimento. Di conseguenza, il metodo non riceve una copia dell'oggetto: se usa il riferimento per modificarlo, la modifica riguarda lo stesso oggetto nell'heap e può essere osservata anche dal codice chiamante.

Ricorsione e fattoriale

Nella ricorsione un metodo richiama sé stesso. Ogni chiamata crea un nuovo record di attivazione sullo stack; quando una chiamata termina, il controllo torna a quella precedente. Il fattoriale è un esempio classico: si implementa con un metodo statico, perché non occorre un oggetto invocato, e tratta separatamente il caso base e il caso ricorsivo.

Variabili statiche e costanti

Oltre alle variabili locali dei metodi, Java permette di dichiarare variabili statiche nella classe. Sono associate alla classe, anziché al record di attivazione di un metodo o a una singola istanza.

La parola chiave final impedisce di riassegnare una variabile dopo la sua inizializzazione. Una variabile statica final può quindi essere usata per rappresentare una costante. Una variabile statica non final può invece essere modificata ed essere condivisa dal codice che ha accesso a quel campo.

I campi si dichiarano nel corpo della classe; l'ordine in cui compaiono rispetto ai metodi non determina la loro visibilità. L'accessibilità dipende dai modificatori, come public. Si accede a un campo statico qualificandolo con il nome della classe, per esempio Classe.nomeVariabile.

Moduli funzionali: responsabilità e uso

Un programma Java può essere composto da più classi, ciascuna memorizzata in un file corrispondente al nome della classe. Una delle classi contiene il metodo main, punto di avvio del programma.

Un modulo funzionale è una classe pensata soprattutto per offrire metodi, variabili e costanti statiche, non per creare istanze. La classe Math ne è un esempio. Nella progettazione di questi moduli, le funzioni dovrebbero essere coese e richiedere tramite parametri i dati necessari, anziché dipendere da molte variabili globali. Valgono inoltre i principi di basso accoppiamento, interfaccia esplicita e information hiding.

Array: definizione e allocazione

Un array è una collezione di elementi dello stesso tipo, accessibili tramite un indice intero. In Java è un oggetto allocato nell'heap; la variabile array contiene un riferimento a quell'oggetto. La dichiarazione e la creazione possono essere scritte così:

int[] numeri;
numeri = new int[10];

La lunghezza di un array Java è fissata quando viene creato. Per ottenere un array più grande o più piccolo si crea un nuovo array e, se necessario, vi si copiano gli elementi: la lunghezza dell'array esistente non cambia.

Lunghezza, indici ed eccezioni

La proprietà length indica la dimensione dell'array. Gli indici validi vanno da zero a length - 1, cioè 0≤i<array.length0 \leq i < array.length. Un accesso fuori da questo intervallo provoca un'eccezione ArrayIndexOutOfBoundsException.

Per esempio, new String[100] crea un array di cento riferimenti a String, inizialmente impostati a null: non crea cento oggetti String.

Riempire e sommare gli elementi

Un ciclo indicizzato consente di riempire l'array e poi di attraversarlo per calcolare una somma. Per memorizzare i quadrati degli interi da 0 a 99:

int[] v = new int[100];
for (int i = 0; i < v.length; i++) {
    v[i] = i * i;
}

int somma = 0;
for (int i = 0; i < v.length; i++) {
    somma += v[i];
}

La condizione del ciclo deve essere strettamente minore di v.length, altrimenti l'ultimo accesso uscirebbe dai limiti dell'array. L'operatore somma += v[i] equivale a riassegnare a somma il suo valore precedente più v[i].

Valori iniziali e array di oggetti

Quando un array viene creato, Java assegna valori iniziali predefiniti agli elementi: zero per i tipi numerici, false per i booleani, il carattere nullo per char e null per gli elementi che sono riferimenti a oggetti.

Si può usare BigInteger come esempio di classe per rappresentare interi di precisione arbitraria. La creazione di new BigInteger[100] alloca un array di cento riferimenti, inizialmente null. Per creare e assegnare i singoli oggetti si può usare un ciclo con BigInteger.valueOf(i).

Gli oggetti restano accessibili finché esiste un riferimento raggiungibile che li conserva. Se non ci sono più riferimenti utilizzabili, diventano disponibili per il garbage collector; non è garantito che vengano rimossi immediatamente.

Inizializzazione inline, array vuoti e null

Quando i valori sono già noti, un array può essere inizializzato inline:

int[] primi = {2, 3, 5, 7, 11, 13};
primi = new int[] {17, 19, 23, 29, 31};

In entrambi i casi l'array è un oggetto nell'heap. È legale creare anche un array di lunghezza zero, per esempio con new int[0]. Un array vuoto esiste e ha length uguale a zero; un riferimento null, invece, non indica alcun array, e accedere a length tramite quel riferimento provoca una NullPointerException.

Ciclo for migliorato

Per leggere tutti gli elementi di un array senza usare esplicitamente gli indici si può usare il ciclo for migliorato:

int somma = 0;
for (int n : numeri) {
    somma += n;
}

A ogni iterazione n riceve il valore dell'elemento corrente. Questa forma è adatta quando si leggono gli elementi; assegnare un nuovo valore alla variabile locale n non modifica la cella dell'array. Se l'elemento è un riferimento a un oggetto, il metodo può comunque modificare l'oggetto attraverso quel riferimento.

Copia e utilità della classe Arrays

La classe Arrays offre metodi statici utili per lavorare con gli array. Arrays.copyOf(array, lunghezza) crea un nuovo array della lunghezza richiesta e copia gli elementi; con array di oggetti la copia è superficiale, perché copia i riferimenti e non duplica ricorsivamente gli oggetti referenziati. Si possono usare inoltre Arrays.sort per ordinare e Arrays.toString per ottenere una rappresentazione testuale. Per array multidimensionali, Arrays.deepToString rappresenta anche i livelli interni.

Argomenti passati al programma

I parametri passati da riga di comando al programma sono contenuti nell'array di stringhe args. Eseguendo java Greeting -c mondo crudele, ad esempio, args[0] contiene -c, args[1] contiene mondo e args[2] contiene crudele; non vi compaiono né il comando java né il nome della classe.

Array multidimensionali e matrici

Un array bidimensionale Java è un array di array. Perciò una variabile come int[][] matrice fa riferimento a un array le cui celle contengono riferimenti ad altri array, che corrispondono alle righe. Le righe possono anche avere lunghezze diverse.

Una matrice rettangolare può essere allocata, per esempio, con new int[4][4]. Il numero di righe si legge con matrice.length; il numero di colonne di una riga specifica si legge con matrice[i].length. Per scandire una matrice si usano due indici: uno per le righe e uno per le colonne. L'inizializzazione inline è pure possibile, indicando ogni riga con un gruppo di valori tra parentesi graffe.

Per sommare due matrici di dimensioni compatibili si crea una matrice risultato e si sommano gli elementi corrispondenti. Il ciclo for migliorato può attraversare anche righe ed elementi, per esempio con for (int[] riga : matrice) seguito da for (int elemento : riga).

Passaggio degli array ai metodi

Poiché un array è un oggetto e il suo riferimento viene passato per valore, il metodo riceve una copia del riferimento che punta allo stesso array. Se modifica gli elementi, le modifiche sono visibili anche al codice chiamante: questo è un possibile side effect da considerare quando si progetta una funzione.

Formule e dimostrazioni

Estrazione di un intero casuale tra 1 e 6

d=⌊6r⌋+1,0≤r<1d = \lfloor 6r \rfloor + 1, \qquad 0 \leq r < 1

Qui rr è il valore restituito da Math.random() e dd è l'intero estratto. La parte intera di 6r6r è compresa tra 0 e 5; sommando 1 si ottiene un valore tra 1 e 6.

Somma di due parametri

s=a+bs = a + b

aa e bb sono i parametri del metodo somma; ss è il valore restituito.

Definizione ricorsiva del fattoriale

0!=1,n!=n⋅(n−1)!per n>00! = 1, \qquad n! = n \cdot (n-1)! \quad \text{per } n > 0

nn è l'intero non negativo di cui si calcola il fattoriale. Il caso n=0n=0 è il caso base; negli altri casi il metodo richiama il fattoriale di n−1n-1.

Somma dei quadrati da 0 a 99

S=∑i=099i2S = \sum_{i=0}^{99} i^2

ii è l'indice dell'array e SS è la somma dei quadrati memorizzati.

Somma di due matrici

Cij=Aij+BijC_{ij} = A_{ij} + B_{ij}

AA e BB sono le matrici di ingresso, CC è la matrice risultato e i,ji,j individuano la riga e la colonna dell'elemento.