Ключевое слово var в Java: что, зачем и почему
Разбираемся, что за var такой и в каких ситуациях он может пригодиться.


robby mccullough / unsplash
Что случилось?
Начиная с версии 10, в Java появилось ключевое слово var. Новая фича — local variable type inference (выведение типа локальной переменной) — не даёт переменным дополнительных возможностей. Впрочем, и ограничений на них не накладывает. Просто разработчикам не нужно теперь писать лишний код при объявлении переменных, когда их тип очевиден из контекста.
В каких случаях тип переменной очевиден?
Если переменной сразу же присваивается значение, для которого компилятор может однозначно понять тип. Вот три типичных ситуации, когда удобно перейти от явного указания типа к var:
1. При создании нового экземпляра класса. Особенно если у этого класса длинное название.
var theLongestNameYouCanEverImagine = new TheLongestNameYouCanEverImagine();
В этом случае компилятор «догадывается», что у переменной theLongestNameYouCanEverImagine должен быть тип TheLongestNameYouCanEverImagine.
2. В заголовке цикла.
for (var i = 1; i < 10; i++){
//здесь что-то интересное происходит
}
Здесь переменной i неявно устанавливается тип int.
Если инициализировать переменную целым числом, то по умолчанию для неё будет определён тип int. Чтобы компилятор решил иначе, нужны подсказки-постфиксы: L — для типа long, F — для float, D — для double, или явное приведение к другому типу.
var a = 2; // тип переменной a — int
var b = 2L; // тип переменной b — long
var c = 2F; // тип переменной c — float
var d = 2D; // тип переменной d — double
var e = (short) 2; //тип переменной e — short
3. В блоке try-with-resources.
void copyFile(File src, File dest) throws IOException {
try (var reader = new BufferedReader(new FileReader(src));
var writer = new BufferedWriter(new FileWriter(dest))) {
String s;
while ((s = reader.readLine()) != null) {
writer.write(s);
writer.newLine();
}
}
}
Тут в заголовке блока инициализируются две локальные переменные: у reader будет тип BufferedReader, у writer — BufferedWriter.
Присвоить значение сразу же означает, что нельзя сначала просто дать var-переменной имя и только следующим оператором инициализировать её:
var x; // не скомпилируется
x = 3; // не скомпилируется
А ещё важно не перепутать окончание оператора с концом строки. Операторы в Java не прерываются переносами строк, поэтому разрешается объявлять переменную в нескольких строках:
var x
= 3; // отлично скомпилируется
Можно ли для любой переменной не указывать тип, если мы сразу её инициализируем?
Нет. На это намекает первая часть названия фичи — local variable. Ключевое слово var можно использовать только с локальными переменными, то есть переменными, которые объявлены:
- внутри конструкторов;
- внутри блоков инициализации;
- внутри методов.
Например:
public class VarExample {
public VarExample() { // var в конструкторе
var constructorVar = "constructorVar";
}
{ // var в блоке инициализации
var initializerVar = "initializerVar";
}
void methodExample() { // var в методе
var methodVar = "methodVar";
}
}
Новый метод объявления неприменим к переменным экземпляра (instance variable) и переменным класса (статическим переменным). То есть вот такие строчки кода компилятор сочтёт ошибочными:
public class VarExample {
var instanceVar = "instanceVar"; // не скомпилируется
static var staticVar = "staticVar"; // не скомпилируется
}
Не путайте переменные, объявленные внутри методов, и переменные — параметры методов. С первыми var использовать можно, со вторыми — нельзя.
public int sum(var x, var y) { // не скомпилируется
return x + y;
}
public int sum2and2() { // а так можно
var x = 2;
var y = 2;
return x + y;
}
Можно ли инициализировать значением null?
И да, и нет. Сам по себе null не даёт компилятору никакой информации о типе — ведь такое значение может быть у любого ссылочного типа. Лучшее, что мог бы сделать компилятор в такой ситуации, — это посчитать, что новая переменная имеет тип Object. Но программистам-то обычно нужно что-то более конкретное, чем Object, — у этого типа не так уж много полезных свойств и методов, так что архитекторы Java решили, что лучше null при использовании с var просто запретить.
Поэтому строка ниже не скомпилируется:
var nullVar = null; // не скомпилируется
Но можно оставить подсказку компилятору:
var nullVar = (String) null;
И это объявление уже допустимо. Правда, не очень понятно, зачем так делать, — проще сразу явно указать тип.
А если с помощью var объявить сразу несколько переменных?
Нет, так не работает. Это просто нужно запомнить: var можно использовать только с одной новой переменной за раз. А вот так нельзя:
int a, var b = 3; // не скомпилируется
var a = 1, b = 2; // тоже не скомпилируется
var a = 1, var b = 2; // и даже так не скомпилируется
int a = 3, b = 2; // да-да, так всё ещё можно
Хочу изменить для var-переменной первоначально заданное значение. Это разрешено?
Да пожалуйста! При объявлении задаётся только начальное значение, и менять его никто не запрещает:
var s = "first value";
s = "second value";
Есть нюанс: ключевое слово var не отменяет правила для переменных с модификатором final. Значения final-переменных, даже введённых новомодным var, менять по-прежнему нельзя.
final var s = "first value";
s = "second value"; // не-не-не, вы же не просто так дописали модификатор final
И тип тоже можно менять?
А вот и нет. Сэкономить на названиях переменных и переиспользовать одну и ту же локальную переменную с разными типами данных не выйдет. Но самим помнить о том, у какой переменной какой тип, не придётся. У компилятора память всё равно лучше, так что он просто не позволит вам совершить такого рода ошибку:
var s = "string value"; // объявили переменную с неявным типом String
s = "another string value"; // присвоили другое строковое значение
s = 100; // не скомпилируется: попытка присвоить строковой
//переменной числовое значение
Не путайте Java с JavaScript: в языке JavaScript тоже есть ключевое слово var. И оно тоже используется для объявления переменных.
Только JavaScript не так строг к типам, как Java. Так что можно, например, присвоить переменной строковое значение, а через пару строчек кода — числовое.
В старом проекте есть переменная с именем var. Придётся ли её переименовывать, если я захочу перейти на десятую Java?
Не придётся, потому что var — это не зарезервированное слово. Оно может использоваться в качестве имени переменной или даже пакета. Вот так:
var var = "var"; // так можно
Правда, это не отменяет тот факт, что var — не лучшее имя для переменной. Оно не очень-то, а точнее, совсем не информативно.
Это ещё хорошо, что у вас в проекте никто не догадался так назвать класс, — вот его бы пришлось переименовывать с переходом к Java 10. Слово var нельзя использовать для именования нового типа — так не получится назвать класс, интерфейс или перечисление (enum).
Подытожим
- Ключевое слово var можно использовать при объявлении локальных переменных в конструкторах, блоках инициализации, методах.
- С ним не получится объявить параметры метода, переменные экземпляра или переменные класса.
- var нужно инициализировать сразу после именования — в одном операторе. При этом можно переносить такое объявление переменной на разные строки.
- Объявлять сразу несколько переменных с помощью var в одном операторе нельзя.
- Инициализировать var-переменную значением null без явного указания типа тоже нельзя.
- Значение var-переменной в дальнейшем меняться может, а вот тип — нет.
- var допустимо использовать в качестве названия переменной, но нельзя так именовать тип: класс, интерфейс или перечисление.
Чтобы ещё лучше разобраться с var, изучите это руководство. В нём собраны советы по использованию local variable type inference «из первых рук» — от сотрудника Oracle.
А если хотите больше узнать о языке Java в целом, приходите на наш курс «Профессия Java-разработчик». Вы научитесь программировать на самом востребованном языке, а мы поможем с трудоустройством.