[Перевод] Var и val в Java?
От переводчика: автор этой заметки — Stephen Colebourne, автор библиотеки Joda Time и Java Time API.
Следует ли добавить вывод типов локальных переменных в Java? Как раз сейчас команда разработчиков языка Java задалась этим вопросом.
JEP-286 предлагает добавить вывод типов локальных переменных, используя новое псевдоключевое слово (интерпретируемое как «зарезервированное наименование типа»):
Явное указание типа локальных переменных зачастую не является необходимым. Разрешив разработчикам опускать его, мы хотим упростить разработку на Java, уменьшив необходимое количество формальностей, но при этом не жертвуя статической типизацией.
Предлагается несколько вариантов ключевых слов:
var
— для изменяемых локальных переменныхval
— для неизменяемых (final) локальных переменныхlet
— для неизменяемых (final) локальных переменныхauto
— этот вариант давайте не будем рассматривать, ладно?
Текущая стратегия реализации подразумевает, что ключевое слово final
будет всё так же допустимо перед любым из этих вариантов, в результате чего неизменяемые переменные можно будет объявить любым из этих способов:
final var
— превращает изменяемую локальную переменную в неизменяемуюfinal val
— здесь модификатор final избыточен и ничего не меняетfinal let
— здесь модификатор final избыточен и ничего не меняет
Следовательно, по факту выбор сводится к одной из следующих комбинаций:
var
иfinal var
var
иval
, ноfinal var
иfinal val
допустимыvar
иlet
, ноfinal var
иfinal let
допустимы
В целом эта возможность не вызывает у меня восторга, и я не уверен, что Java от этого станет лучше. Конечно, при работе в IDE недостаток информации о типах будет сглаживаться. Однако, я полагаю, что это во многих случаях затруднит рецензирование кода, так как оно обычно производится без помощи IDE. Следует также отметить, что соглашение по программированию на C# не рекомендует чрезмерно пользоваться этой возможностью:
Не используйте
var
, когда тип выражения справа от присваивания не является очевидным.
Не полагайтесь на имя переменной при определении её типа. Оно может не соответствовать истине.
Несмотря на вышесказанное, я подозреваю, что шансов остановить реализацию этого улучшения у меня немного. Поэтому оставшаяся часть статьи посвящена тому, как выбрать правильный вариант из предложенных.
Когда было объявлено об этом улучшении, приверженцы Скалы и Котлина, естественно, стали советовать использовать var
и val
. Однако хотя изучить опыт предшественников всегда полезно, это не означает, что для Java такой вариант будет идеален.
Главная причина того, что наилучший вариант для Java может быть другим, — это история. В Скале и Котлине эта возможность была с самого начала, а в Java — нет. И я хочу продемонстрировать, почему из-за исторических причин использование val
или let
не подходит для Java.
Рассмотрим следующий код:
public double parSpread(SwapLeg leg) {
Currency ccyLeg = leg.getCurrency();
Money convertedPv = presentValue(swap, ccyLeg);
double pvbp = legPricer.pvbp(leg, provider);
return -convertedPv.getAmount() / pvbp;
}
Вывод типов локальных переменных прекрасно бы здесь сработал. Но предположим, что тип переменной в одной из строчек неясен при чтении кода и мы решили указать его явно, следуя рекомендациям C#:
public double parSpread(SwapLeg leg) {
val ccyLeg = leg.getCurrency();
Money convertedPv = presentValue(swap, ccyLeg);
val pvbp = legPricer.pvbp(leg, provider);
return -convertedPv.getAmount() / pvbp;
}
Всё прекрасно, скажете вы. Но что если этот код пишет команда, где соглашения подразумевают явно помечать локальные переменные словом final
?
public double parSpread(final SwapLeg leg) {
val ccyLeg = leg.getCurrency();
final Money convertedPv = presentValue(swap, ccyLeg);
val pvbp = legPricer.pvbp(leg, provider);
return -convertedPv.getAmount() / pvbp;
}
Внезапно получилась путаница. В одних местах для обозначения неизменяемой переменной используется слово final
, а в других местах — слово val
. Именно это смешение всё портит и при этом такой проблемы не стоит в Скале и Котлине.
(Возможно, вы не используете final
для каждой локальной переменной? Я вот не использую. Но я знаю, что это вполне разумный стандарт, придуманный для улучшения безопасности разработки и уменьшения ошибок.)
А вот альтернативный вариант:
public double parSpread(final SwapLeg leg) {
final var ccyLeg = leg.getCurrency();
final Money convertedPv = presentValue(swap, ccyLeg);
final var pvbp = legPricer.pvbp(leg, provider);
return -convertedPv.getAmount() / pvbp;
}
Для Java это гораздо более последовательно. Слово final
продолжает оставаться повсеместным механизмом для обозначения неизменяемой переменной. А если вы, как и я, не считаете необходимым везде подписывать final
, вы просто удаляете его:
public double parSpread(final SwapLeg leg) {
var ccyLeg = leg.getCurrency();
Money convertedPv = presentValue(swap, ccyLeg);
var pvbp = legPricer.pvbp(leg, provider);
return -convertedPv.getAmount() / pvbp;
}
Я понимаю, какие возражения возникнут у многих читателей в этом месте. Мол, должно быть два новых «ключевых слова», одно для изменяемых и одно для неизменяемых локальных переменных и оба должны быть одинаковой длины (или даже слово для изменяемых должно быть длиннее), чтобы подталкивать людей чаще использовать неизменяемую версию.
Но в Java не всё так просто. Слово final
существует многие годы. Если закрыть глаза на этот факт, код превратится в некрасивое и непоследовательное месиво.
Мне лично вывод типов локальных переменных не нравится вообще. Но если мы всё-таки решили его делать, надо, чтобы он хорошо вписывался в существующий язык.
Моя позиция заключается в том, что val
и let
не подходят для Java, потому что уже существует слово final
и оно имеет вполне понятный смысл. Хотя вариант с var
и final var
не идеален, я считаю, что это единственная из предложенных комбинаций, которая подходит уже существующему языку.