Var java что это
Перейти к содержимому

Var java что это

  • автор:

Устаревшее ключевое слово «var»

Информация, приведенная в этой статье, полезна для понимания старых скриптов.

Мы не пишем современный код таким образом.

В самой первой главе про переменные мы ознакомились с тремя способами объявления переменных:

let и const ведут себя одинаково по отношению к лексическому окружению, области видимости.

Но var – это совершенно другой зверь, берущий своё начало с давних времён. Обычно var не используется в современных скриптах, но всё ещё может скрываться в старых.

Если в данный момент вы не работаете с подобными скриптами, вы можете пропустить или отложить прочтение данной главы, однако, есть шанс, что вы столкнётесь с var в будущем.

На первый взгляд, поведение var похоже на let . Например, объявление переменной:

function sayHi() < var phrase = "Привет"; // локальная переменная, "var" вместо "let" alert(phrase); // Привет >sayHi(); alert(phrase); // Ошибка: phrase не определена

…Однако, отличия всё же есть.

Для «var» не существует блочной области видимости

Область видимости переменных var ограничивается либо функцией, либо, если переменная глобальная, то скриптом. Такие переменные доступны за пределами блока.

if (true) < var test = true; // используем var вместо let >alert(test); // true, переменная существует вне блока if

Так как var игнорирует блоки, мы получили глобальную переменную test .

А если бы мы использовали let test вместо var test , тогда переменная была бы видна только внутри if :

if (true) < let test = true; // используем let >alert(test); // Error: test is not defined

Аналогично для циклов: var не может быть блочной или локальной внутри цикла:

for (var i = 0; i < 10; i++) < // . >alert(i); // 10, переменная i доступна вне цикла, т.к. является глобальной переменной

Если блок кода находится внутри функции, то var становится локальной переменной в этой функции:

function sayHi() < if (true) < var phrase = "Привет"; >alert(phrase); // срабатывает и выводит "Привет" > sayHi(); alert(phrase); // Ошибка: phrase не определена (видна в консоли разработчика)

Как мы видим, var выходит за пределы блоков if , for и подобных. Это происходит потому, что на заре развития JavaScript блоки кода не имели лексического окружения. Поэтому можно сказать, что var – это пережиток прошлого.

«var» допускает повторное объявление

Если в блоке кода дважды объявить одну и ту же переменную let , будет ошибка:

let user; let user; // SyntaxError: 'user' has already been declared

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

var user = "Пётр"; var user; // ничего не делает, переменная объявлена раньше // . нет ошибки alert(user); // Пётр

Если дополнительно присвоить значение, то переменная примет новое значение:

var user = "Пётр"; var user = "Иван"; alert(user); // Иван

«var» обрабатываются в начале запуска функции

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

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

function sayHi() < phrase = "Привет"; alert(phrase); var phrase; >sayHi();

…Технически полностью эквивалентен следующему (объявление переменной var phrase перемещено в начало функции):

function sayHi() < var phrase; phrase = "Привет"; alert(phrase); >sayHi();

…И даже коду ниже (как вы помните, блочная область видимости игнорируется):

function sayHi() < phrase = "Привет"; // (*) if (false) < var phrase; >alert(phrase); > sayHi();

Это поведение называется «hoisting» (всплытие, поднятие), потому что все объявления переменных var «всплывают» в самый верх функции.

В примере выше if (false) условие никогда не выполнится. Но это никаким образом не препятствует созданию переменной var phrase , которая находится внутри него, поскольку объявления var «всплывают» в начало функции. Т.е. в момент присвоения значения (*) переменная уже существует.

Объявления переменных «всплывают», но присваивания значений – нет.

Это проще всего продемонстрировать на примере:

function sayHi() < alert(phrase); var phrase = "Привет"; >sayHi();

Строка var phrase = «Привет» состоит из двух действий:

  1. Объявление переменной var
  2. Присвоение значения в переменную = .

Объявление переменной обрабатывается в начале выполнения функции («всплывает»), однако присвоение значения всегда происходит в той строке кода, где оно указано. Т.е. код выполняется по следующему сценарию:

function sayHi() < var phrase; // объявление переменной срабатывает вначале. alert(phrase); // undefined phrase = "Привет"; // . присвоение - в момент, когда исполнится данная строка кода. >sayHi();

Поскольку все объявления переменных var обрабатываются в начале функции, мы можем ссылаться на них в любом месте. Однако, переменные имеют значение undefined до строки с присвоением значения.

В обоих примерах выше вызов alert происходил без ошибки, потому что переменная phrase уже существовала. Но её значение ещё не было присвоено, поэтому мы получали undefined .

IIFE

В прошлом, поскольку существовал только var , а он не имел блочной области видимости, программисты придумали способ её эмулировать. Этот способ получил название «Immediately-invoked function expressions» (сокращенно IIFE).

Это не то, что мы должны использовать сегодня, но, так как вы можете встретить это в старых скриптах, полезно понимать принцип работы.

IIFE выглядит следующим образом:

(function() < var message = "Привет"; alert(message); // Привет >)();

Здесь создаётся и немедленно вызывается Function Expression. Так что код выполняется сразу же и у него есть свои локальные переменные.

Function Expression обёрнуто в скобки (function <. >) , потому что, когда JavaScript встречает «function» в основном потоке кода, он воспринимает это как начало Function Declaration. Но у Function Declaration должно быть имя, так что такой код вызовет ошибку:

// Пробуем объявить и сразу же вызвать функцию function() < // ();

Даже если мы скажем: «хорошо, давайте добавим имя», – это не сработает, потому что JavaScript не позволяет вызывать Function Declaration немедленно.

// ошибка синтаксиса из-за скобок ниже function go() < >(); // 

Так что скобки вокруг функции – это трюк, который позволяет объяснить JavaScript, что функция была создана в контексте другого выражения, а значит, что это Function Expression: ей не нужно имя и её можно вызвать немедленно.

Помимо круглых скобок существуют и другие способы сообщить JavaScript, что мы имеем в виду Function Expression:

// Способы создания IIFE (function() < alert("Круглые скобки вокруг функции"); >)(); (function() < alert("Круглые скобки вокруг всего выражения"); >()); !function() < alert("Выражение начинается с логического оператора НЕ"); >(); +function() < alert("Выражение начинается с унарного плюса"); >();

Во всех перечисленных случаях мы объявляем Function Expression и немедленно запускаем его. Ещё раз отметим: в настоящее время необходимости писать подобный код нет.

Итого

Существует 2 основных отличия var от let/const :

  1. Переменные var не имеют блочной области видимости, они ограничены, как минимум, телом функции.
  2. Объявления (инициализация) переменных var производится в начале исполнения функции (или скрипта для глобальных переменных).

Есть ещё одно небольшое отличие, относящееся к глобальному объекту, мы рассмотрим его в следующей главе.

Эти особенности, как правило, не очень хорошо влияют на код. Блочная область видимости – это удобно. Поэтому много лет назад let и const были введены в стандарт и сейчас являются основным способом объявления переменных.

Первый контакт с «var» в Java 10

image

Java 10 будет выпущен 20 марта 2018 года, и все фичи, которые должны быть в этом релизе, уже объединены в основную ветку разработки. Одним из самых интересных нововведений Java 10 безусловно является вывод типа локальной переменной (JEP 286). Это дает вам возможность сократить объявления переменных используя новое ключевое слово var:

var users = new ArrayList();

И это все, спасибо за внимание!

Нет, я уверен, что вам интересно узнать больше. Под катом я расскажу, где применяется var, а где нет, как это влияет на читаемость кода и что произошло с val.

Замена объявлений типа с помощью var

В качестве разработчика Java мы привыкли дважды вводить типы, один раз для объявления переменной и второй раз для следующего за ней конструктора:

URL codefx = new URL("http://codefx.org");

Мы также часто объявляем типы переменных, которые используются только один раз:

URL codefx = new URL("http://codefx.org"); URLConnection connection = codefx.openConnection(); Reader reader = new BufferedReader( new InputStreamReader(connection.getInputStream())); 

Это не особенно страшно, но всё же несколько избыточно. И хотя IDE могут помочь в написании такого кода, читаемость страдает, когда имена переменных перескакивают вправо-влево, потому что названия типов имеют разную длину или когда разработчики избегают объявления промежуточных переменных, потому что объявления типов будут отвлекать на себя много внимания, не принося никакой пользы.

Начиная с Java 10 у разработчиков появится альтернатива — они могут позволить компилятору вывести тип с помощью var:

var codefx = new URL("http://codefx.org"); var connection = codefx.openConnection(); var reader = new BufferedReader( new InputStreamReader(connection.getInputStream())); 

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

Как вы видите, это экономит несколько символов при наборе текста, но, что более важно, он дедуплицирует избыточную информацию и аккуратно выравнивает имена переменных, что облегчает их чтение. Стоимость очевидна: некоторые типы переменных, например connection, не сразу очевидны. IDE могут, конечно же, показывать их по требованию, но это не помогает ни в какой другой среде (например, при просмотре кода в браузере — на stackoverflow или даже в этой статье — прим. перев.).

Кстати, если вы беспокоитесь о конфликтах с методами и переменными с именем var: не нужно. Технически, var — это не ключевое слово, а зарезервированное имя типа, то есть его можно использовать только в тех местах, где компилятор ожидает имя типа, но во всех остальных местах он является допустимым идентификатором. Это означает, что только классы, называемые var, больше не будут работать, но это не особо частый случай.

Вывод типа локальной переменной выглядит как простая функция, но это обманчиво. Возможно, у вас уже есть некоторые вопросы:

  • хм, это Java или JavaScript?
  • где я могу это использовать?
  • не ухудшится ли читаемость кода?
  • почему не val или let?

Нет, это не JavaScript

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

Например, вот результат декомпиляции IntelliJ (фактически Fernflower) файла класса с примером URL:

URL codefx = new URL("http://codefx.org"); URLConnection connection = codefx.openConnection(); BufferedReader reader = new BufferedReader( new InputStreamReader(connection.getInputStream())); 

Это байт в байт тот же результат, как если бы я сам объявил типы. Фактически вывод типов существует только во время компиляции и никак не влияет на итоговый байт-код, что также означает отсутствие какого-либо влияния на производительность. Так что расслабьтесь, это не Javascript, и никто не собирается превращать 0 в бога.

Если вы все еще обеспокоены тем, что отсутствие явных типов делает код хуже, у меня есть вопрос для вас: вы когда-нибудь писали лямбда-выражения, не определяя типы аргументов?

rhetoricalQuestion.answer(yes -> "уловили мысль?");

Где использовать var (и где не нужно)

Название JEP 286, «вывод типа локальной переменной», немного намекает на то, где var можно использовать: для локальных переменных. Точнее, для «локальных объявлений переменных с инициализаторами», так что следующий код работать не будет:

// неа var foo; foo = "Foo"; 

Код должен быть таким: var foo = ''Foo''. Даже тогда это распространяется не на все случаи, так как var не будет работать с так называемыми «poly expressions», такими как лямбда-выражения и ссылки на методы, тип которых компилятор определяет в отношении ожидаемого типа:

// ничто из этого не работает var ints = ; var appendSpace = a -> a + " "; var compareString = String::compareTo 

Единственное подходящее место, помимо локальных переменных — это цикл for:

var numbers = List.of("a", "b", "c"); for (var nr : numbers) System.out.print(nr + " "); for (var i = 0; i < numbers.size(); i++) System.out.print(numbers.get(i) + " "); 

Это означает, что поля, сигнатуры методов и выражения catch все еще требуют ручного объявления типа.

// неа private var getFoo()

Устранение ошибок «Действие на расстоянии»

То, что var может использоваться только локально, является не техническим ограничением, а конструктивным решением. Конечно, было бы неплохо, если бы он работал так:

// держим кулачки за то, чтобы компилятор вывел List var users = new ArrayList(); // но нет, здесь ошибка компиляции: users = new LinkedList<>(); 

Компилятор мог бы легко просмотреть все присвоения и вывести наиболее конкретный тип, который подходит для каждого из них, но он не делает этого. Команда JDK хотела избежать ошибок «действия на расстоянии», что означает, что изменение кода в некотором месте не должно приводить к, казалось бы, несвязанной ошибке далеко в другой части системы.

В качестве примера рассмотрим следующее:

// выведено как `int` var (id < 100) < // очень длинная ветвь // к сожалению не имеет собственного метода >else < // ох, ещё больше кода. >

Пока что всё идёт… Я не хочу говорить «хорошо», но вы знаете, что я имею в виду. Я уверен, что вы видели такой код. Теперь добавим эту строку:

id = "124"

Что произойдет? Это не риторический вопрос, подумайте об этом.

Ответ заключается в том, что if-условие вызывает ошибку, потому что id больше не будет int и поэтому не может сравниться с 100. Эта ошибка находится на довольно большом расстоянии от изменения, вызвавшего ее. Кроме того, это непредвиденное следствие простого присваивания значения переменной.

С этой точки зрения, решение ограничить вывод типа до непосредственного объявления имеет смысл.

Почему не могут быть выведены типы полей и методов?

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

Так как не приватные поля и методы становятся частью контракта класса, и поскольку они не должны просто так меняться, их типы не выводятся. Конечно, исключение можно было бы сделать для приватных полей или методов, но это сделало бы вывод типов довольно запутанным.

Основная идея заключается в том, что локальные переменные являются деталями реализации и не могут ссылаться на «далекий» код, что уменьшает необходимость строгого, явного и подробного определения их типа.

Предпосылки появления var

Давайте посмотрим за кулисы и узнаем, почему был введен var, как он должен повлиять на читаемость и почему нет val (или let), сопровождающего его. Если вас интересует наиболее подробная информация, посмотрите дискуссии JEP 286, часто задаваемые вопросы и список рассылки Project Amber.

Но зачем?!

Java склонна быть довольно многословной, особенно по сравнению с более молодыми языками, это является одним из слабых мест языка и общей темой для критики новичками и опытными разработчиками Java. Project Amber, в рамках которого был разработан var, направлен на «изучение и инкубацию небольших, ориентированных на продуктивность разработки функций Java-языка», и цель состоит в том, чтобы в целом сократить рутину, связанную с написанием и чтением кода на Java.

Вывод типа локальной переменной согласуется с этой целью. С точки зрения написания кода, это явно упрощает объявление переменных, хотя я бы предположил, что добрая половина моих объявлений генерируется средой IDE, либо во время рефакторинга, либо потому что проще написать вызов конструктора или метода, а затем создать для него переменную.

Помимо упрощения объявлений это также делает их более податливыми. Что я имею в виду? Объявления могут быть довольно уродливыми, особенно если речь идет о названиях обобщённых классов в корпоративных приложениях.

InternationalCustomerOrderProcessor> orderProcessor = createInternationalOrderProcessor(customer, order); 

Это чертовски длинное имя типа, которое выталкивает имя переменной в конец и оставляет вас либо с растянутой до 150 символов строкой, либо инициализацией переменной в новой строке. Оба варианта отстой, если вы нацелены на удобочитаемость.

var orderProcessor = createInternationalOrderProcessor(customer, order);

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

Короче говоря, var — это про сокращение многословия и рутины, а не об экономии символов.

А что относительно читаемости?

Теперь перейдем к читаемости. Несомненно, когда типы отсутствуют, читаемость должна ухудшиться, не так ли? Вообще говоря, да. Когда вы пытаетесь понять, как работает часть кода, типы являются важным компонентом. И даже если бы IDE разработали функции, позволяющие отображать все выведенные типы, это все равно было бы более косвенным, чем если бы они всегда присутствовали в исходнике.

Таким образом, var сразу приносит нам недостаток читаемости и должен компенсировать это. Один из способов — выравнивание имен переменных:

// с явными типами No no = new No(); AmountIncrease more = new BigDecimalAmountIncrease(); HorizontalConnection jumping = new HorizontalLinePositionConnection(); Variable variable = new Constant(5); List names = List.of("Max", "Maria"); // с выведенными типами var no = new No(); var more = new BigDecimalAmountIncrease(); var jumping = new HorizontalLinePositionConnection(); var variable = new Constant(5); var names = List.of("Max", "Maria"); 

Имена типов важны, но имена переменных могут быть важнее. Типы описывают общую концепцию в контексте всей экосистемы Java (для классов JDK), общий вариант использования (библиотека или фреймворк) или бизнес-домен (приложение) и, следовательно, всегда будут иметь общие имена. Переменные, с другой стороны, определены в конкретном и очень малом контексте, в котором их имя может быть очень точным.

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

Как говорилось выше, другое улучшение читаемости может происходить из-за того, что объявлено больше промежуточных переменных, поскольку это связано со снижением издержек при записи и чтении.

Поиск стиля

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

Брайан Гетц (Brian Goetz), архитектор языка Java в Oracle и ответственный за Project Amber, дал первую эвристику:

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

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

Почему нет val/let?

Многие языки с var также предлагают дополнительное ключевое слово для неизменяемых переменных. Обычно это называется val, иногда let, но Java 10 не имеет ни того, ни другого, и вместо этого мы должны использовать final var. Вот несколько причин:

  • неизменяемость важна, но для локальных переменных она важна в меньшей степени
  • начиная с Java 8 у нас есть концепция эффективного final, уже и без того приближающая нас к неизменяемым локальным переменным
  • там, где var получил всеобщее одобрение (74% категорически, 12% умеренно) ответная реакция как на var / val, так и на var / let была весьма неоднозначной

Чтож, возможно в будущем… До тех пор мы должны использовать final var.

Подводя итоги

При объявлении локальных переменных вы можете использовать var вместо имени класса или интерфейса, чтобы позволить компилятору вывести тип. Это работает только в том случае, если переменная немедленно инициализируется, например, как в var s = "". Индексы для циклов также могут быть объявлены с помощью var. Тип, выводимый компилятором, помещается в байт-код, поэтому во время выполнения ничего не меняется — Java все еще является статически типизированным языком.

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

Хотя бездумное использование var может сделать код хуже, тем не менее это шанс для Java-разработчиков написать более читабельный код, найдя новый баланс между шумом объявлений и сложностью вложенных / последовательных выражений.

Ключевое слово var в Java: что, зачем и почему

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

robby mccullough / unsplash

Екатерина Степанова

Екатерина Степанова

Фулстек-разработчик. Любимый стек: Java + Angular, но в хорошей компании готова писать хоть на языке Ада.

Что случилось?

Начиная с версии 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, у writerBufferedWriter.

Присвоить значение сразу же означает, что нельзя сначала просто дать 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; // не скомпилируется

Но можно оставить подсказку компилятору:

FAQ по var в Java

Локальные переменные — это "рабочие лошадки" Java. Они используются для хранения промежуточных результатов вычислений. И, в отличие от полей, объявляются, инициализируются и используются в одном блоке. Для понимания кода часто более важны имя и инициализатор, чем тип локальной переменной. В имени и инициализаторе столько же информации, сколько и в типе:

Person person = new Person();

Вместо явного указания типа можно использовать var :

var person = new Person();

Компилятор определяет тип локальной переменной по инициализатору. Это особенно важно, когда тип параметризован wildcard , или упоминается в инициализаторе. Использование var может сделать код более кратким без ущерба для удобочитаемости, а в некоторых случаях может улучшить читаемость благодаря устранению избыточности.

Вопрос 2. Делает ли это Java динамически типизированным языком? Это то же самое, что и var в JavaScript?

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

В компиляторе Java выведение типов присутствует уже много лет. Например, параметры лямбда-выражения в Java 8 не нуждаются в явном указании типа, так как компилятор определяет их по тому, как используется лямбда-выражение:

List list = . list.stream().filter(p -> p.getAge() > 18) . 

В приведенном выше фрагменте кода тип параметра p в лямбде выводится как Person . Если класс Person будет изменен так, что в нем больше не будет метода getAge , или в списке будет храниться что-то отличное от Person , то выведение типа завершится ошибкой при компиляции.

Вопрос 3. Переменная var неявно final?

Нет. Локальные переменные, объявленные с помощью var , по умолчанию не являются final . Однако к var можно добавить модификатор final :

final var person = new Person();

Для final var в Java нет никакого сокращения. Например, в Scala для объявления неизменяемых переменных используется val . В Scala это хорошо работает, потому что все переменные (и локальные, и поля) объявляются с помощью синтаксиса следующего вида.

val name : type
var name : type

Указывать " : type " в объявлении необязательно — это зависит от того, хотите ли вы использовать выведение типа или нет. В Scala выбор между изменяемостью и неизменяемостью ортогонален к выведению типов.

В Java var можно использовать только там, где требуется выведение типа. Его нельзя использовать, если тип объявлен явно. Если в Java добавить val , то его тоже можно было бы использовать только там, где используется выведение типов. В Java нельзя было бы использовать var или val для управления иммутабельностью при явном объявлении типа.

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

Использование ключевых слов var/val для управления иммутабельностью — это то, что действительно стоит позаимствовать из Scala в Java. Однако в Java это было бы гораздо менее полезно, чем в Scala.

Вопрос 4. Не будут ли плохие разработчики злоупотреблять var, чтобы писать ужасный код?

Да, плохие разработчики будут писать ужасный код, что бы мы ни делали. Отказ от var не помешает им это сделать. Но при правильном применении выведение типов позволит писать более качественный код.

Один из моментов, благодаря которому var может побудить разработчиков писать более качественный код, заключается в том, что var снижает накладные расходы на объявление новой переменной. Разработчики часто избегают объявления новой переменной, если это требует больших затрат, и создают сложные конструкции, ухудшающие читаемость, только из-за того, чтобы избежать объявления дополнительных переменных. С var накладные расходы на извлечение части кода в именованную переменную уменьшаются, поэтому разработчики с большей вероятностью будут это делать, что приведет к более чистому коду.

Часто программисты начинают очень активно использовать новые возможности языка, возможно, злоупотребляя ими, и требуется некоторое время, чтобы сообщество выработало рекомендации по разумному использованию.

Начиная с выведения типа локальной переменной (Local Variable Type Inference, LVTI), мы публикуем материалы о назначении и рекомендации по использованию (например, данный FAQ и LVTI Style Guidelines) почти одновременно с появлением этой функциональности. Мы надеемся, что это ускорит понимание в сообществе, когда разумно использовать var , и поможет избежать злоупотребления.

Вопрос 5. Где можно использовать var?

var может использоваться при объявлении локальных переменных, включая индексные переменные цикла for и ресурсные переменные оператора try-with-resources .

var нельзя использовать для полей, параметров методов и возвращаемых типов методов. Причина заключается в том, что типы в этих местах явно присутствуют в class-файлах и в спецификациях Javadoc. При выведении типа изменение инициализатора может легко привести к изменению предполагаемого типа переменной. Для локальных переменных это не проблема, так как область видимости локальных переменных ограничена, и их типы напрямую не записываются в class-файлы. Однако для полей, параметров методов и типов, возвращаемых методом, выведение типа может легко вызвать проблему.

Например, возвращаемый тип метода был выведен из выражения в операторе return . Изменение реализации метода может привести к изменению типа выражения в return . Это, в свою очередь, может изменить тип возвращаемого значения. Что может привести к бинарной несовместимости или несовместимости в исходном коде. Такие несовместимые изменения не должны возникать из-за безобидных на вид изменений в реализации.

Предположим, что также будет выводиться тип поля. Тогда изменение инициализатора поля может привести к изменению типа поля, что может неожиданно нарушить рефлексивный код.

Выведение типа допустимо в реализации, но не в API. API-контракты должны быть объявлены явно.

Как насчет private-полей и методов, которые не являются частью API? Теоретически мы могли бы реализовать поддержку var для private-полей и возвращаемых типов для private-методов, не опасаясь, что это вызовет несовместимость из-за раздельной компиляции и динамической линковки. Но для простоты мы решили ограничить область выведения типов. Если расширить границы и включить отдельные виды полей и типы, возвращаемые некоторыми методами, то это сделает все значительно более сложным и трудным для понимания, и лишь незначительно более полезным.

Вопрос 6. Почему для var всегда нужен инициализатор в правой части?

Тип переменной вычисляется на основе инициализатора. Это означает, что var можно использовать только при наличии инициализатора. Можно было бы сделать выведение типа из присвоений переменной, но это значительно усложнило бы реализацию и потенциально могло привести к вводящим в заблуждение или трудно диагностируемым ошибкам. Для простоты мы определили var так, чтобы для выведения типа использовалась только локальная информация.

Допустим, мы разрешили выведение типа на основе присваивания в нескольких местах, отдельно от объявления переменной. Тогда рассмотрим следующий пример:

var order; . order = "first"; . order = 2;

Если тип был выбран на основе, например, первого присваивания, то это может привести к ошибке в другом операторе, весьма удаленном от местоположения настоящей ошибки. (Иногда это называют проблемой "дальнодействия".)

В качестве альтернативы можно выбрать тип, совместимый со всеми присваиваниями. И в этом случае можно ожидать, что выведенный тип будет Object , как общий суперкласс String и Integer . К сожалению, ситуация сложнее. Поскольку и String , и Integer являются Serializable и Comparable , общий супертип будет пересечением типов, что-то вроде

Serializable & Comparable>

Обратите внимание, что невозможно явно объявить переменную этого типа. Также это приводит к упаковыванию (boxing), когда order присваивается 2, что может быть неожиданным и нежелательным.

Чтобы избежать подобных проблем, проще потребовать вывод типа с использованием явного инициализатора.

Вопрос 7. Почему нельзя использовать var с null?

Посмотрите на такое объявление (оно некорректно):

var person = null; // ОШИБКА

Литерал null обозначает значение специального типа null (JLS 4.1), который является подтипом всех ссылочных типов в Java. Единственным значением типа null является сам null , поэтому единственное значение, которое может быть присвоено переменной типа null — это null . Это не очень полезно.

Можно ввести специальное правило, чтобы объявление var , инициализированное значением null , имело тип Object . Но возникает вопрос о намерениях программиста. Предположим, что переменная инициализируется значением null , чтобы позже ей присвоить какое-либо другое значение. Но в этом случае маловероятно, что определение типа переменной как Object будет правильным.

Вместо того чтобы создавать какие-то специальные правила для обработки этого случая, мы его запретили. Если вам нужна переменная типа Object , объявите ее явно.

Вопрос 8. Можно ли использовать var с diamond-оператором справа?

Да, это работает, но, скорее всего, не так, как вы ожидаете. Пример:

var list = new ArrayList<>();

Тип списка будет ArrayList . В общем, лучше указывать явный тип слева с diamond-оператором справа или использовать var слева с явным типом справа. См. особенности использования var с diamond-оператором и дженериками в LVTI Style Guidelines (Руководство по стилю LVTI).

Приглашаем всех желающих на открытое занятие «Реляционные базы данных для начинающих Java-разработчиков». Поговорим о месте реляционных баз данных в архитектуре информационных систем. Рассмотрим основные компоненты и возможности РСУБД на примере PostgreSQL. Сделаем обзор основных технологий по работе с реляционными БД в Java (JDBC, JPA/Hibernate, Spring Data и др.) Регистрация по ссылке.

  • Блог компании OTUS
  • Программирование
  • Java

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *