Varargs: i chiaroscuri della magia

Java 5 e i varargs

VarargsDalla versione 1.5 in Java sono disponibili i varargs, un elegante metodo di avere metodi con zero o più parametri di uno specifico tipo.

Vediamo subito un metodo di esempio che prende in ingresso un parametro di tipo varargs:

        public static int somma(int... args)
        {
                int sum = 0;

                for (int arg : args)
                {
                        sum += arg;
                }
                return sum;
        }

Il metodo di cui sopra esegue la somma dei numeri passati in ingresso: se invoco somma(1, 2, 3) ottengo 6, se invoco somma() ottengo 0. Internamente il tutto viene gestito creando un array la cui dimensione è il numero di argomenti passati e il cui contenuto sono gli argomenti stessi, array che poi è passato al metodo. Il fatto che ci pensi Java a farlo ci consente di scrivere metodi con numero qualunque di argomenti (dello stesso tipo) in modo semplice ed elegante.

Se il mio metodo ha almeno un parametro obbligatorio?

Supponiamo di voler scrivere un metodo che calcola il minimo di n interi passati in ingresso. In questo caso il numero di parametri zero non è comtemplato, anche perché ha poco senso.

Potremmo usare i varargs e fare controlli all’interno del metodo per controllare che la dimensione dell’array sia maggiore di zero, ma in questo caso si entra in quello che tecnicamente si chiama bloodbath (parola importante del dizionario di ogni Javista).

Il modo corretto di gestire questo caso è quello di prevedere il prima parametro obbligatorio come argomento a parte:

        public int min(int primoArg, int... altriArgs)
        {
                int min = primoArg;

                for (int arg : altriArgs)
                        if (arg < min)
                                min = arg;
                return min;
        }

Cool! Adesso cerco tutti i metodi che prendevano in ingresso un array e li trasformo in varargs

Dite la verità: se non conoscevate i varargs avete pensato questo! Invece questo refactoring può portare in fretta ad un JavaDoh! (e non mi riferisco allo strumento per scrivere la documentazione in Java).

I varargs vanno usati quando lo scopo è applicare un metodo a un numero variabile di parametri, non per rendere più comodi metodi che prendevano in ingresso un array. Vediamo subito un esempio.

Lo sventurato caso di Arrays.asList

Prima di Java 5 Arrays.asList prendeva in input un array e lo trasformava in una lista. Con Java 5 prende in ingresso un varargs è quindi è possibile scrivere:

List coseNonJaviste = Arrays.asList("Andrea", "Fabio", "Manuele");

Purtroppo prima di Java 5 era anche uso comune utilizzare questo idioma per stampare su console il contenuto di un array:

System.out.println(Arrays.asList(myArray));

Infatti Array eredita il toString di Object e se si voleva scrivere qualcosa di sensato bisognava passare dalle liste. Tuttavia se si utilizzava accidentalmente questa linea di codice su array di tipi primitivi in Java < 1.5 non compilava e venivamo avvisati, mentre in Java > 1.4, a causa della modifica a Arrays.asList, compila correttamente, ma si ottiene un output che non aiuta per nulla (ad esempio qualcosa come [[I@3e25a5]] se si usano int come tipi primitivi). Ora voi direte: non è per questo che siamo stati scacciati dall'Eden; è vero, tuttavia questo esempio dimostra che modificare un metodo che prende in input un array in uno che accetta un varargs può avere effetti collaterali tutt'altro che scontati.

Per fortuna i progettisti della Sun avevano anche introdotto in Java 5 un nuovo modo di stampare il contenuto di un array che non dipendeva dall'idioma di cui sopra:

// il parametro non è un varargs, ma un array!
System.out.println(Arrays.toString(myArray));

Un occhio alle performance

Se si utilizzano i varargs in situazioni dove le performance sono critiche è bene ricordare che ad ogni invocazione del metodo in questione abbiamo l'allocazione e l'inizializzazione di un array. Talvolta possono presentarsi circostanze in cui possiamo ottimizzare: siamo ad esempio riusciti a determinare statisticamente che il 95% delle volte il metodo è invocato con meno di 3 parametri. Allora prevediamo esplicitamente anche i metodi che prendono in ingresso zero, uno due e tre parametri, così da evitare il sovraccarico computazionale dei varargs in questi casi:

public void metodo() {}
public void metodo(int i1) {}
public void metodo(int i1, int i2) {}
public void metodo(int i1, int i2, int i3) {}
public void metodo(int i1, int i2, int i3, int...altri) {}

Conclusioni

I varargs hanno aggiunto dolcezza nel mondo, ma è bene non esagerare. D'altre parte anche il gelato è tanto buono, ma mangiandone un chilo può presentare side effects! Gli antichi romani (il cui impero, ignaro del Java, è durato a lungo) avrebbero detto: "In medias res stat virtus!".

Manuele Piastra

Sono uno Scrum Master e Project Manager i cui skill tecnici sono focalizzati al momento sullo sviluppo di applicazioni Java EE su IBM Websphere 7.0 utilizzando JSF (RichFaces), JPA (EclipseLink) ed EJB3. Presso OmniaGroup ricopro il ruolo di Training Manager: seleziono il personale tecnico, mi occupo della sua crescita formativa organizzando Corsi e Workshop sia interni che esterni, molti dei quali hanno visto me come docente. -