Java – Variabili locali con “var” da Java 10

Java 10 ha introdotto una piccola ma interessante novità nel linguaggio. Si tratta della possibilità di usare la parola “var” per dichiarare le variabili locali senza dover specificare il tipo, sfruttando in pratica un meccanismo di “inferenza” (deduzione) del tipo ad opera del compilatore.

Questa nuova funzionalità permette in sostanza di scrivere ad esempio:

var msg = "Ciao";
invece di
String msg = "Ciao";

Oppure ad esempio:

var nomi = Arrays.asList("Mario", "Roberto");
invece di
List<String> nomi = Arrays.asList("Mario", "Roberto");

Ci sono però delle regole e restrizioni ben precise su questa nuova sintassi. Alcune di queste regole sono abbastanza ovvie, per il fatto che il compilatore deve dedurre un tipo. Altre invece sono forse meno ovvie ma sono comunque da prendere e accettare così come sono.

L’obiettivo di questo articolo è di descrivere queste regole e restrizioni usando anche degli esempi per fare maggior chiarezza.

Concetti, regole e restrizioni

1) La parola “var” non è una keyword

Su questo aspetto il JLS (Java Language Specification) è molto chiaro (vedere sezione §3.9, Keywords): var non è una keyword (“parola chiave”), cioè non è una parola “riservata” del linguaggio. Invece è semplicemente un identificatore che ha un significato “speciale” come tipo solo nel contesto della dichiarazione di una variabile locale.

Questo vuol dire che var può essere ancora usato come normale identificatore in qualunque altra dichiarazione di variabile o parametro, ad esempio:

public static int quadrato(int var) {   // Ok
    return var * var;
}
BigInteger var = BigInteger.valueOf(123);   // Ok

Per assurdo, è anche possibile fare una cosa del tipo:

var var = "Ciao";       // Sì ... è lecito!
System.out.println(var);

Anche se, personalmente, preferirei evitare di mettere “var var” così.

2) Si può usare solo per variabili “locali”

Per come è stata architettata, questa nuova sintassi con var si può utilizzare solo per le variabili locali. Quindi si può usare:

  • per le variabili locali in qualunque metodo/costruttore e in generale qualunque “blocco” di codice
  • per la variabile che si può dichiarare nel ciclo for “classico” e anche nel enhanced-for (detto for-each) disponibile da Java 5
  • per le variabili che si possono dichiarare nel costrutto try-with-resource introdotto in Java 7

Mentre invece non si può utilizzare:

  • per i parametri di metodi/costruttori
  • per la dichiarazione del tipo di ritorno di un metodo
  • per le variabili “di istanza” (non static) e “di classe” (static)
  • per il parametro dichiarato nel catch

3) Può avere delle annotation e il modificatore final

La variabile dichiarata con la nuova sintassi var può anche avere, naturalmente, delle eventuali annotation e il modificatore final, che la rende di fatto una “costante”.

final var num = 123.45678;   // Ok, il compilatore deduce che num è una costante double

4) Deve sempre avere una inizializzazione esplicita con un valore

Dal momento che il compilatore deve poter dedurre subito un tipo, la variabile dichiarata con var deve avere una inizializzazione esplicita nel punto della dichiarazione, non può essere fatta in un secondo momento. Il valore assegnato può essere una costante o il valore di ritorno di un metodo o in generale una qualunque espressione, purché il tipo di tale valore sia chiaramente deducibile dal compilatore.

var num;     // NO, errore!
num = 123;   //

var num = 123;              // Ok, il compilatore deduce che num è un int
var num2 = Math.random();   // Ok, il compilatore deduce che num2 è un double

Sostanzialmente per lo stesso motivo appena detto, non ha senso inizializzare la variabile dichiarata con var usando una espressione che è la invocazione di un metodo con tipo di ritorno void, in quanto non c’è appunto alcun valore restituito.

var res = System.out.println("ciao");   // NO, errore!

5) Non si può inizializzare con un null letterale

Il null non è un tipo di dato, è un valore “letterale” che rappresenta l’assenza di un riferimento ad un oggetto. Inoltre, concettualmente, null è il sottotipo di tutti i tipi reference. Quindi con null il compilatore non potrebbe dedurre alcun tipo.

var x = null;     // NO, errore!

6) Non si possono definire più variabili in una singola dichiarazione

Quando si dichiarano le variabili nel modo “classico”, è lecito definire più variabili nella stessa dichiarazione, cioè ad esempio:

int a = 123, b = 789;   // Ok, è sempre stato lecito

Nella nuova sintassi con var invece questo è illegale, in quanto si può dichiarare una sola variabile.

var a = 123, b = 789;     // NO, errore!

var a = 123;    // Ok
var b = 789;    // Ok

7) Non si possono usare le parentesi [ ] nella dichiarazione della variabile

Quando si dichiara una variabile di tipo array nel modo “classico”, si devono specificare delle coppie di parentesi [] secondo il numero di dimensioni dell’array.

int[] arr = new int[10];
double[][] mat = new double[10][20];

Nella nuova sintassi con var invece le parentesi [] non si possono specificare vicino alla variabile.

var arr[] = new int[10];            // NO, errore!
var[] arr = new int[10];            // NO, errore!
var mat[][] = new double[10][20];   // NO, errore!

var arr = new int[10];          // Ok, il compilatore deduce che arr è un int[]
var mat = new double[10][20];   // Ok, il compilatore deduce che mat è un double[][]

8) Non si può utilizzare la inizializzazione con { } per gli array

Quando si dichiara una variabile di tipo array nel modo “classico”, è lecito inizializzare subito l’array con dei valori usando la sintassi breve che utilizza semplicemente le parentesi { } per racchiudere i valori.

int[] arr = { 10, 20, 30 };        // Ok, è sempre stato lecito
String[] strings = { "A", "B" };   // Ok, è sempre stato lecito

Nella nuova sintassi con var invece questa forma non si può utilizzare, in quanto il compilatore non è in grado di dedurre il tipo soltanto dai valori.

Se con var si vuole inizializzare subito un array con dei valori, è necessario ricorrere alla forma chiamata anonymous array che in generale si può usare anche quando si vuole assegnare un nuovo array con inizializzazione ad una variabile già dichiarata in precedenza.

var arr = { 10, 20, 30 };       // NO, errore!
var strings = { "A", "B" };     // NO, errore!

var arr = new int[] { 10, 20, 30 };        // Ok con un anonymous array
var strings = new String[] { "A", "B" };   // Ok con un anonymous array

9) Non si può inizializzare direttamente con una lambda expression

Le lamba expression sono una grossa nuova funzionalità introdotta in Java 8 per poter definire delle “funzioni” in maniera più compatta ed espressiva. Una lamba expression deve sempre essere associata ad una functional interface e il compilatore deduce la “forma” della funzione applicando il target typing ovvero basandosi sul contesto in cui si trova la lamba expression.

IntBinaryOperator intAdder = (a, b) -> a + b;

In questa riga sopra il contesto è un “assegnamento”. Il compilatore deduce che la funzione riceve due int e restituisce un int poiché è assegnata ad un IntBinaryOperator il cui singolo metodo astratto è:

int applyAsInt(int left, int right)

Con questa nuova sintassi con var invece il tipo della variabile è proprio quello che manca e deve essere dedotto! Quindi non avrebbe alcun senso assegnare direttamente una lamba expression ad una variabile dichiarata con var in quanto non ci sarebbe un tipo deducibile. Se si vuole assegnare una lamba expression, è necessario come minimo applicare un cast, in modo da “aiutare” il compilatore a dedurre la forma della lamba expression e di conseguenza dedurre il tipo della variabile dichiarata con var.

var intAdder = (a, b) -> a + b;   // NO, errore!

var intAdder = (IntBinaryOperator) (a, b) -> a + b;         // Ok
var strJoiner = (BinaryOperator<String>) (a, b) -> a + b;   // Ok

10) La parola “var” non si può più usare come nome di tipo

Questa restrizione può sembrare inizialmente un po’ strana a prima vista. Ma è abbastanza logica da comprendere. Dal momento che var ora si può usare al posto di un tipo nella dichiarazione di una variabile ... allora non è più possibile dichiarare un tipo (classe, interfaccia, ecc...) chiamato esattamente var.

public class var { }       // NO, errore!

public interface var { }   // NO, errore!

Questa comunque è la restrizione meno problematica, per un motivo molto semplice: dichiarare un tipo con il nome esattamente var è una violazione delle convenzioni sui nomi. Esistono da sempre tutta una serie di convenzioni sulle denominazioni che sono ampiamente riconosciute e accettate dalla comunità degli sviluppatori Java.

Secondo queste convenzioni, i nomi di classi, interfacce (anche di enum e annotation) dovrebbero iniziare con la lettera maiuscola. Quindi, seguendo e rispettando queste convenzioni, nessuno dovrebbe mai definire un tipo che si chiama var con la iniziale minuscola.

Conclusioni

In questo articolo ho cercato di elencare tutti i concetti e le regole essenziali sulla dichiarazione delle variabili locali con la nuova sintassi var introdotta in Java 10. Ci sarebbero diverse altre cose da dire, anche particolari, su questa funzionalità e cercherò sicuramente di descriverle in ulteriori articoli.