Ключевое слово var в Java (вывод типов локальных переменных)
Используйте var для вывода типов локальных переменных в Java: когда это улучшает читаемость, а когда нет.
Начиная с Java 10, вы можете объявить локальную переменную с помощью var и позволить компилятору вывести её тип из инициализатора. var greeting = "hello"; полностью идентично — включая скомпилированный байткод — записи String greeting = "hello";: тип по-прежнему String, вы просто не написали его дважды. Это вывод типов локальных переменных: синтаксическое удобство, которое убирает избыточные имена типов, не делая Java динамически типизированным языком. Используемый грамотно, он устраняет шум; используемый небрежно — скрывает именно ту информацию, которая нужна читателю.
На этой странице рассказывается, что var делает и чего не делает, где именно он допустим, в каких случаях он оправдан, в каких вредит читаемости, а также приведена запускаемая программа, доказывающая, что выведенные типы соответствуют ожиданиям.
var — это вывод типов, а не динамическая типизация
Самое важное: var — это не новый тип «что угодно». Компилятор читает правую часть, определяет статический тип и фиксирует его. С этого момента переменная так же строго типизирована, как если бы вы написали тип вручную — вы не можете присвоить ей значение несовместимого типа, и выведенный тип зафиксирован на этапе компиляции.
var name = "Ada"; // name has static type String, forever
name = "Lovelace"; // fine, still a String
name = 42; // compile error: int cannot be assigned to Stringvar — это зарезервированное имя типа, а не ключевое слово — вы по-прежнему можете использовать var как имя переменной или метода (хотя не стоит). Вывод типа активируется только в позиции объявления локальной переменной.
Где var допустим — и где нет
var работает только для локальных переменных с инициализатором. Компилятору нужна правая часть, чтобы прочитать тип; без неё выводить не из чего.
| Позиция | var допустим? | Причина |
|---|---|---|
| Локальная переменная с инициализатором | Да | Инициализатор предоставляет тип |
Индекс/элемент в циклах for | Да | Выражение цикла предоставляет тип |
| Переменная в try-with-resources | Да | Выражение ресурса предоставляет тип |
| Локальная переменная без инициализатора | Нет | Нечего выводить |
| Поля / переменные экземпляра | Нет | Вывод только для локальных — по замыслу |
| Параметры методов | Нет | Значения предоставляют вызывающие, а не инициализаторы |
| Возвращаемые типы методов | Нет | Та же причина, что и у параметров |
Инициализация только null | Нет | У null нет конкретного типа |
| Параметры лямбда-выражений (без типа) | Особый случай | (var x, var y) -> ... допустимо с Java 11 |
var x; // error: cannot infer type, no initializer
var nothing = null; // error: null has no type to infer
public var field = 1; // error: var not allowed on fields
void m(var p) { } // error: var not allowed on parametersРеальная выгода: устранение громоздких дженериков
var оправдывает своё существование, когда имя типа длинное, повторяющееся или насыщено дженериками. Классический случай — объявление, в котором тип записывается полностью по обе стороны от =:
// Before: the type name is written twice
Map<String, List<Customer>> byCity = new HashMap<String, List<Customer>>();
// After: the right side already says everything
var byCity = new HashMap<String, List<Customer>>();Он также хорошо работает с итераторами, Map.Entry из HashMap и другими многословными типами, которые не добавляют ясности при явном написании:
for (var entry : byCity.entrySet()) { // Map.Entry<String, List<Customer>>
System.out.println(entry.getKey() + " -> " + entry.getValue().size());
}Когда НЕ следует использовать var
var помогает, когда тип очевиден из правой части, и мешает, когда это не так. Если читатель вынужден мысленно выполнять код, чтобы понять тип, — напишите тип явно.
var result = service.process(input); // unclear: what does process return?
Order result = service.process(input); // clear: an Order
var flag = true; // fine, obviously boolean
var count = list.size(); // fine, obviously intОстерегайтесь ловушки числовых литералов: var выводит тип самого литерала, а не тот тип, который вы могли иметь в виду.
var n = 100; // int, not long — for a long you must write 100L or long n
var f = 3.14; // double, not float
byte b = 1; // explicit type narrows; var b = 1 would be intИзбегайте var, когда это приводит к потере намеренного типа интерфейса. var list = new ArrayList<String>(); типизирует list как ArrayList<String>, а не List<String> — локально это нормально, но если вы намерены программировать через интерфейс, скажите об этом явно.
Рабочий пример, который можно запустить
Эта программа использует var во всех допустимых позициях — простые значения, обобщённая карта, цикл for-each, индексный цикл for — и применяет getClass().getSimpleName(), чтобы доказать: выведенные типы в точности соответствуют тому, что подразумевали правые части.
Что можно извлечь из запуска:
greeting.getClass().getSimpleName()выводитString, доказывая, чтоvar greeting = "hello"создал настоящийString—var— это вывод типа на этапе компиляции, а во время выполнения объект в точности соответствует тому, что подразумевал литерал; ничего динамического.count + 1 = 43иprice * 2 = 19.98подтверждают правила вывода числовых типов:42сделалоcountтипомint,9.99сделалоpriceтипомdouble. Тип литерала — а не ваше намерение — решает всё; это та ловушка, о которой нужно помнить, когда вам нуженlongилиfloat.scores type = HashMapпоказывает, чтоvarзахватил конкретный тип правой частиHashMap, а не интерфейсMap; угловые скобки<String, List<Integer>>в правой части дали компилятору всё необходимое, хотя левая часть содержала лишьvar.total chars = 10получается изfor (var name : names), гдеnameбыл выведен какString, поэтомуname.length()вычислился корректно (3 + 3 + 4) —varработает в цикле for-each, выводя тип элемента из итерируемого.0..4 sum = 10получается изfor (var i = 0; ...), гдеiбыл выведен какintиз литерала0; индексный цикл — одно из наиболее чистых мест для использованияvar, потому что тип очевиден.
Практика
Связанные темы
- Переменные в Java — объявление и инициализация локальных переменных.
- Область видимости переменных — почему
varнамеренно ограничен локальной областью видимости. - Дженерики — громоздкие имена типов, которые
varлучше всего скрывает. - Цикл
for— гдеvar iиvar entryчитаются чисто.