Java 10 — первая близкая встреча с «механизмом вывода типа var»

задняя часть

这里写图片描述

введение

Официально Java 10 выйдет 20 марта 2018 года. (Моя большая Java 9 мгновенно стала Vista....) По слухам, 14 декабря 2017 года официально завершилось основное слияние разработки версий. На данный момент среди немногих официально выпущенных новых функций Java 10局部变量类型推断(local-variable type inference)Это определенно нравится толпе. Это будет то, что мы часто используем в JS.varПеременные были введены в языковые функции, чтобы избавить нас от этих подробных объявлений переменных. Давай, протяни правую руку, вот код, который ты точно еще не писал:

var users = new ArrayList<User>();

Все еще не могу не вздохнуть о величии Java, «интегрирующем величие мира», нет слов, чтобы описать это лучше. Итак, увидев такой код, я думаю, вам должно быть интересно узнать о нем больше, в этой статье мы обсудимvarГде он применяется, как влияет на читабельность и что происходит во время вывода типа.

Замена традиционных объявлений переменных на var

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

URL lovnx = new URL("https://github.com/Lovnx");

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

URL lovnx = new URL("https://github.com/Lovnx");
URLConnection connection = lovnx.openConnection();
Reader reader = new BufferedReader(
	    new InputStreamReader(connection.getInputStream())
    );

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

Начиная с Java 10, у разработчиков есть возможность объявлять переменные какvarчтобы компилятор самостоятельно определял его тип:

var lovnx = new URL("https://github.com/Lovnx");
var connection = lovnx.openConnection();
var reader = new BufferedReader(
    new InputStreamReader(connection.getInputStream()));

Компилятор обрабатываетvarКогда создается переменная, она обнаруживает объявление кода справа и использует свой тип слева, что и происходит во время инициализации. ВТФ? Сладкое до грустного? JIT по-прежнему использует предполагаемый тип результата при компиляции в байт-код.

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

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

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

  • Это Java или JavaScript?
  • Где его следует использовать?
  • Разве это не разрушает удобочитаемость строгой типизации в прошлом?
  • Почему нет val или let?

Посмотрите вниз, вы получите ответ.

это не джаваскрипт

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

Например, следующий код предназначен для абзаца выше сvarРезультат декомпиляции байт-кода кода:

URL lovnx = new URL("https://github.com/Lovnx");
URLConnection connection = lovnx.openConnection();
Reader reader = new BufferedReader(
	    new InputStreamReader(connection.getInputStream())
    );

По факту,varЖизненный цикл 's существует только в компиляторе, а не для компонентов среды выполнения, которые он включает, поэтому вы можете быть уверены, что это не повлияет на производительность. Следовательно, это не похоже на JavaScript, чтобы разобрать его как ключевое слово, и никто не может сделать это за одну ночь.

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

rhetoricalQuestion.answer(yes -> "see my point?");

Сфера применения вар

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

//nope
var foo;
foo = "Foo";

должно бытьvar foo = "Foo". Приведенный выше пример не охватывает все, что нельзя использоватьvarагрегатное выражение. Например, для лямбда-выражений и ссылок на методы компилятор определит тип на основе ожидаемого типа, и следующие случаи не будут работать:

//nope
var ints = {0, 1, 2};
var appendSpace = a -> a + " ";
var compareString = String::compareTo

В дополнение к локальным переменным есть еще одно применениеforВнутри цикла:

//right
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 по-прежнему должны отображать объявления типов, что неверно:

// nope
private var getFoo() {
    return "foo";
}

Избегайте необъяснимых ошибок

varТо, что его можно использовать только для локальных переменных, не является техническим ограничением команды JDK, это определяется характеристиками языка Java. Как следующее:

// cross fingers that compiler infers List<User>
var users = new ArrayList<User>();
// but it doesn't, so this is a compile error:
users = new LinkedList<>();

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

Ниже приведен пример:

// inferred as `int`
var id = 123;
if (id < 100) {
    // very long branch; unfortunately
    // not its own method call
} else {
    // oh boy, much more code...
}

В приведенном выше коде нет ничего плохого, теперь мы добавляем эту строку кода к условному телу:

id = "124"

Что случится? Это не напыщенный вопрос, подумайте об этом.

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

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

Почему его нельзя использовать для объявления свойств класса и типов возвращаемых значений метода?

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

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

В конце концов, основная причина заключается в том, что локальные переменные — это всего лишь некоторые детали реализации, на которые нельзя ссылаться извне, и это снижает потребность в строгом, явном и подробном определении (строгом типировании) их типа. (Я думаю, что это лень ===)

Предыстория появления var в Java 10

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

But why?!

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

Так появился механизм вывода типа локальной переменной (Local-variable type inference). При написании кода очевидно делать объявления переменных краткими, хотя до сих пор я думаю, что это неоднозначное благо по сравнению с возможностями автогенерации IDE, например, во время перезаписи, написания конструктора или объявления типа для возвращаемого значения метода.

Преимущества var не только упрощают объявление локальных переменных, но и позволяют коду дополнять друг друга. Почему? Если вы занимались или работаете в сфере корпоративного развития, эти имена могут показаться вам довольно уродливыми. Вот типичный каштан:

InternationalCustomerOrderProcessor<AnonymousCustomer, SimpleOrder<Book>> orderProcessor = createInternationalOrderProcessor(customer, order);

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

var orderProcessor = createInternationalOrderProcessor(customer, order);

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

Короче говоря, var — это синтаксический сахар для упрощения громоздкого синтаксиса Java.

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

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

Неудобочитаемость компенсируется var из других мест Один из способов — сделать так, чтобы имя переменной совпадало (хе-хе, 00 выглядит лучше):

// with explicit types
No no = new No();
AmountIncrease<BigDecimal> more = new BigDecimalAmountIncrease();
HorizontalConnection<LinePosition, LinePosition> jumping =
    new HorizontalLinePositionConnection();
Variable variable = new Constant(5);
List<String> names = List.of("Max", "Maria");
 
// with inferred types
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), общие варианты использования (библиотеки или платформы) и бизнес-области (приложения). Но суть всего этого — одно маленькое имя переменной за другим. Как и JS, разве он не популярен в мире?

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

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

Finding A Style

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

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

Почему бы не использовать val/let?

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

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

Этот результат на самом деле немного разочаровывает, не лучше ли заменить final var на val или let?

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

Суммировать

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

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

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

Слегка измененная исходная ссылка