Как использовать новый синтаксис «var», представленный в Java 10

Java 10, выпущенная в марте 2018 г., представила небольшую, но полезную функцию: возможность объявлять локальные переменные с помощью слова «var» вместо объявления типа переменной. Это особый вид выведения типа, поскольку компилятор должен выводить (дедуцировать) тип переменной из присвоенного значения.

На практике можно сделать что-то вроде:

var message = "Hello";                   // Java 10+
var bignum = BigInteger.valueOf(1234);   // Java 10+

вместо:

String message = "Hello";
BigInteger bignum = BigInteger.valueOf(1234);

Есть несколько хорошо известных правил и ограничений в отношении этого нового вида синтаксиса. Некоторые из этих правил довольно просты и очевидны, в то время как другие немного менее интуитивны. Однако все эти правила никуда не делись, так что вы должны их выучить и запомнить. В этой статье описаны все правила в практичной и лаконичной форме.

1) Слово «вар» не является ключевым словом

Спецификация языка Java (JLS) совершенно ясно говорит об этом. Раздел §3.9. Ключевые слова JLS ясно указывает, что var не является ключевым словом, то есть это не зарезервированное слово языка. Вместо этого var — это ограниченный идентификатор со специальным значением только в контексте объявлений локальных переменных.

Это означает, что слово «var» по-прежнему может использоваться как обычный идентификатор в других объявлениях переменных или параметров. Например, это совершенно законно:

public static int square(int var) {   // Ok
    return var * var;
}

а также:

BigInteger var = BigInteger.valueOf(1234);   // Ok

и даже (но, пожалуйста, НЕ этого не делайте!):

var var = "Hello";        // Yes, it's legal!! 
System.out.println(var);

2) Может использоваться только для «локальных» переменных

Новый синтаксис с var можно использовать только с локальными переменными. Таким образом, вы можете использовать его для:

  • локальные переменные в любом методе, конструкторе и экземпляре/статическом блоке инициализации
  • переменная, объявленная в «классическом» цикле for, а также в цикле enhanced-for («для каждого»), доступном, начиная с Java 5
  • переменные, объявленные в конструкции try-with-resource (доступно, начиная с Java 7)

Однако новый синтаксис var нельзя использовать для:

  • параметры методов/конструкторов
  • объявленный возвращаемый тип метода
  • переменные «экземпляр» (не-static) и «класс» (static)
  • параметр предложения catch

3) Может иметь аннотации и/или финальный модификатор

Переменная, объявленная с использованием нового синтаксиса var, может иметь аннотации, а также модификатор final, который делает ее «константой».

final var num = 123.45678;   // Ok, num is a constant of double type

4) Всегда должен быть явно инициализирован значением

Поскольку компилятор должен выводить общеизвестный тип, переменная, объявленная с новым синтаксисом var, всегда должна быть инициализирована одновременно с объявлением. Инициализацию нельзя выполнить на втором этапе.

// NO, ERROR! ❌
var num;
num = 123;
// OK ✔️
var num = 123;              // Ok, num is deduced of type int
var num2 = Math.random();   // Ok, num2 is deduced of type double

По той же причине совершенно не имеет смысла присваивать результат метода void, так как значения нет.

var res = System.out.println("hello");   // NO, ERROR! ❌

5) Не может быть инициализирован буквальным нулем

«Буквальный» нуль (null) — это значение типа ссылка, обозначающее отсутствие объекта. Кроме того, null технически является подтипом всех остальных ссылочных типов. Таким образом, с литеральным нулем компилятор не мог вывести какой-либо тип.

var x = null;   // NO, ERROR! ❌

6) Невозможно определить больше переменных в одном объявлении

Классический («старый») способ объявления переменных позволяет определить (и, возможно, инициализировать) более одной переменной в одной и той же инструкции объявления.

int a = 123, b = 789;   // Ok, always legal

С новым синтаксисом var незаконно определять больше переменных в одном операторе объявления.

// NO, ERROR ❌
var a = 123, b = 789;
// OK ✔️
var a = 123;   // Ok
var b = 789;   // Ok

7) Нельзя использовать квадратные скобки [ ] рядом с именем переменной

При объявлении переменной типа массив классическим способом необходимо поставить возле имени переменной (желательно и условно справа от типа) пару скобок [] для каждого измерения массива .

int[] arr = new int[10];
double[][] matrix = new double[4][8];

С новым синтаксисом var недопустимо заключать имена переменных в скобки, поскольку тип массива автоматически определяется компилятором.

// NO, ERROR! ❌
var arr[] = new int[10];
var[] arr = new int[10];
var matrix[][] = new double[4][8];
// OK ✔️
var arr = new int[10];           // Ok, arr is of type int[]
var matrix = new double[4][8];   // Ok, matrix is of type double[][]

8) Невозможно инициализировать массивы с помощью короткого синтаксиса {}

Когда вы объявляете переменную типа массив классическим способом, допустимо инициализировать массив, используя «короткую» форму, в которой значения заключаются в фигурные скобки { и }.

int[] numbers = { 10, 20 };        // Ok, always legal
String[] strings = { "A", "B" };   // Ok, always legal

С новым синтаксисом var незаконно использовать короткую форму инициализации, потому что компилятор не сможет вывести тип массива только из значений (по крайней мере, не во всех случаях, если быть точным). ).

Если вы хотите инициализировать массив с использованием нового синтаксиса var, вы должны использовать так называемый анонимный массив, в котором используется ключевое слово new.

// NO, ERROR! ❌
var numbers = { 10, 20 };
var strings = { "A", "B" };
// OK ✔️
var numbers = new int[] { 10, 20 };       // Ok with anonymous array
var strings = new String[] { "A", "B" };  // Ok with anonymous array

9) Не может быть инициализирован лямбда-выражением

Лямбда-выражения — важная и замечательная функция, представленная в Java 8 для более компактного и выразительного определения «функций». Лямбда-выражение должно всегда быть связано с функциональным интерфейсом (концептуально особым видом Java interface), поскольку компилятор должен вывести сигнатуру функции из функционального интерфейса.

Например:

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

В этом случае компилятор делает вывод, что функция принимает два int и возвращает int, потому что у IntBinaryOperator есть метод:

int applyAsInt(int left, int right)

Этот вид вывода типа прямо противоположен новому синтаксису var, потому что с var компилятор должен вывести тип из присвоенного значения. По этой причине невозможно напрямую присвоить лямбда-выражение переменной, объявленной с синтаксисом var.

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

Если вы действительно хотите присвоить лямбда-выражение переменной с помощью var, вы должны немного «помочь» компилятору, вставив явное приведение, чтобы компилятор мог определить правильный тип функции, например:

var intAdder = (IntBinaryOperator) (a, b) -> a + b;   // OK ✔️

10) Слово «var» больше нельзя использовать в качестве имени типа.

Сначала это ограничение может показаться немного странным, но его легко понять. Поскольку «var» теперь можно использовать вместо типа, больше невозможно объявить новый тип (например, класс или интерфейс), используя именно слово «var».

public class var { }   // NO, ERROR! ❌
public interface var { }   // NO, ERROR! ❌

В любом случае, это ограничение менее важно и менее проблематично по простой причине: объявление типа с именем «var» является явным нарушением стандартных соглашений об именах.

Всегда существовал ряд соглашений об именах, широко признанных и принятых сообществом разработчиков Java. Согласно этим соглашениям, имена типов Java должны начинаться с заглавной буквы (например, Pperson, Book). Таким образом, если вы следуете и уважаете эти соглашения, вы никогда не должны использовать точное имя «var» для типа Java.