Рубрики
Без рубрики

Введение в проект Янтарный

Краткий и практический обзор проекта «Янтарь для Java».

Автор оригинала: Graham Cox.

Введение в проект Янтарный

1. Что такое проект Янтарный

Проект Янтарный является текущей инициативой разработчиков Java и OpenJDK, направленной на внесение небольших, но существенных изменений в JDK, чтобы сделать процесс разработки более . Это продолжается с 2017 года и уже внесли некоторые изменения в Java 10 и 11, а другие планируется включить в Java 12 и еще больше в ближайшие будущие релизы.

Все эти обновления упакованы в виде JEPs – Схема предложения по улучшению JDK.

2. Доставленные обновления

На сегодняшний день Project Amber успешно внесли некоторые изменения в выпущенные в настоящее время версии JDK – JEP-286 и JEP-323 .

2.1. Локальный вывод переменного типа

Java 7 представила Алмазный оператор как способ облегчить работу дженериков с . Эта функция означает, что нам больше не нужно писать общую информацию несколько раз в одном и том же заявлении, когда мы определяем переменные:

List strings = new ArrayList(); // Java 6
List strings = new ArrayList<>(); // Java 7

Java 10 включала в себя завершенную работу над JEP-286, что позволило нашему Java-коду определить локальные переменные без необходимости дублировать информацию о типе, где компилятор уже имеет доступ . В более широком сообществе это называется вар ключевое слово и приносит аналогичную функциональность Java, как это доступно на многих других языках.

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

var strings = new ArrayList();

В вышеуказанном переменный строки определяется как тип ArrayList () , но без необходимости дублировать информацию на той же строке.

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

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

Мы можем использовать локальный вывод типа только тогда, когда предоставляем фактический тип в рамках . Он намеренно предназначен для работы, когда значение явно нулевой, когда значение не предоставляется вообще, или когда предоставленное значение не может определить точный тип – например, определение Lambda:

var unknownType; // No value provided to infer type from
var nullType = null; // Explicit value provided but it's null
var lambdaType = () -> System.out.println("Lambda"); // Lambda without defining the interface

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

Optional name = Optional.empty();
var nullName = name.orElse(null);

В этом случае nullName сделает вывод о типе Струнные потому что это то, что возвращение типа name.orElse () есть.

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

2.2. Локальный вывод о переменном типе для Ламбдаса

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

В Java 10 мы можем определить функции Lambda одним из двух способов – либо путем прямого объявления типов, либо путем их полного опускания:

names.stream()
  .filter(String name -> name.length() > 5)
  .map(name -> name.toUpperCase());

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

Java 11 позволяет это происходить , так что мы можем вместо этого написать:

names.stream()
  .filter(var name -> name.length() > 5)
  .map(var name -> name.toUpperCase());

Это согласуется с использованием вар ввесь в другом месте в нашем кодовом .

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

numbers.stream()
    .reduce(0, (var a, var b) -> a + b); // Valid

numbers.stream()
    .reduce(0, (var a, b) -> a + b); // Invalid

numbers.stream()
    .reduce(0, (var a, int b) -> a + b); // Invalid

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

3. Неизбежные обновления

В дополнение к обновлениям, которые уже доступны в выпущенных JDKs, предстоящий релиз JDK 12 включает в себя одно обновление – ДЖЕП-325.

3.1. Выражения коммутатора

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

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

С этими изменениями, синтаксис для определения выписки коммутатора похож на заявление , с использованием -> символ. Это находится между совпадением случая и кодом, который должен быть выполнен:

switch (month) {
    case FEBRUARY -> System.out.println(28);
    case APRIL -> System.out.println(30);
    case JUNE -> System.out.println(30);
    case SEPTEMBER -> System.out.println(30);
    case NOVEMBER -> System.out.println(30);
    default -> System.out.println(31);
}

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

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

switch (month) {
    case FEBRUARY -> {
        int days = 28;
    }
    case APRIL -> {
        int days = 30;
    }
    ....
}

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

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

switch (month) {
    case FEBRUARY -> System.out.println(28);
    case APRIL, JUNE, SEPTEMBER, NOVEMBER -> System.out.println(30);
    default -> System.out.println(31);
}

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

Это позволяет для переключить выражение для решения значения, а затем использовать это значение в других – например, задание:

final var days = switch (month) {
    case FEBRUARY -> 28;
    case APRIL, JUNE, SEPTEMBER, NOVEMBER -> 30;
    default -> 31;
}

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

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

4. Предстоящие изменения

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

4.1. Сырьевые струнные буквы

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

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

JEP-326 вводит новый тип буквального струны под названием Raw String Literals . Они заключены в отметки backtick вместо двойных кавычек и могут содержать любые символы на всех внутри них.

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

Например:

// File system path
"C:\\Dev\\file.txt"
`C:\Dev\file.txt`

// Regex
"\\d+\\.\\d\\d"
`\d+\.\d\d`

// Multi-Line
"Hello\nWorld"
`Hello
World`

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

Новые сырье Строки Literals также позволяют нам включить backticks себя без осложнений . Количество backticks используется для запуска и конца строки может быть до тех пор, как хотелось бы – это не должно быть только один backtick. Строка заканчивается только тогда, когда мы достигаем равной длины backticks. Так, например:

``This string allows a single "`" because it's wrapped in two backticks``

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

4.2. Остатки Ламбды

JEP-302 вводит некоторые небольшие улучшения в том, как lambdas работы.

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

Java 8 внес изменения, так что использование подчеркивать в качестве имени является предупреждением. Java 9 затем прогрессировал это, чтобы стать ошибкой, а не, останавливая нас от их использования на всех. Это предстоящее изменение позволяет им для параметров lambda без причинения каких-либо конфликтов. Это позволило бы, например, следующий код:

jdbcTemplate.queryForObject("SELECT * FROM users WHERE user_id = 1", (rs, _) -> parseUser(rs))

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

Другим важным изменением в этом усовершенствование является возможность параметров lambda для теневых имен из текущего контекста . В настоящее время это запрещено, что может заставить нас написать несколько меньше, чем идеальный код. Например:

String key = computeSomeKey();
map.computeIfAbsent(key, key2 -> key2.length());

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

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

String key = computeSomeKey();
map.computeIfAbsent(key, key -> key.length());

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

Например, в настоящее время компилятор считает следующие методы двусмысленными :

m(Predicate ps) { ... }
m(Function fss) { ... }

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

Этот JEP может устранить этот недостаток и позволить эту перегрузку рассматриваться явно.

4.3. Сопоставление шаблонов

ДЖЕП-305 вносит улучшения в том, как мы можем работать с instanceof оператора и автоматического типа принуждения.

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

if (obj instanceof String) {
    String s = (String) obj;
    // use s
}

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

Это усовершенствование вносит аналогичную корректировку в instanceof оператора, как это было ранее сделано в попробовать с ресурсами на Java 7 . С этим изменением сравнение, отбрасываемое и переменная декларация становятся вместо этого единым утверждением:

if (obj instanceof String s) {
    // use s
}

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

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

if (obj instanceof String s) {
    // can use s here
} else {
    // can't use s here
}

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

String s = "Hello";
if (obj instanceof String s) {
    // s refers to obj
} else {
    // s refers to the variable defined before the if statement
}

Это также работает в рамках той же если пункт , так же, как мы полагаемся на нулевой Проверяет:

if (obj instanceof String s && s.length() > 5) {
    // s is a String of greater than 5 characters
}

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

4.4. Тела кратких методов

Проект JEP 8209434 это предложение по поддержке упрощенных определений методов , таким образом, что похож на то, как lambda определения работы.

Прямо сейчас, мы можем определить Lambda в трех различных : с телом, как единое выражение, или в качестве ссылки метода:

ToIntFunction lenFn = (String s) -> { return s.length(); };
ToIntFunction lenFn = (String s) -> s.length();
ToIntFunction lenFn = String::length;

Тем не менее, когда дело доходит до написания фактических тел метода класса, мы в настоящее время должны выписать их в полном объеме .

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

Например, метод getter не нуждается в полном теле метода, но может быть заменен одним выражением:

String getName() -> name;

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

int length(String s) = String::length

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

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

5. Улучшенные энумы

JEP-301 ранее планировалось стать частью проекта Янтарный. Это принесло бы некоторые улучшения enums, явно позволяя для отдельных элементов enum иметь различные общие сведения типа .

Например, это позволит:

enum Primitive {
    INT(Integer.class, 0) {
       int mod(int x, int y) { return x % y; }
       int add(int x, int y) { return x + y; }
    },
    FLOAT(Float.class, 0f)  {
       long add(long x, long y) { return x + y; }
    }, ... ;

    final Class boxClass;
    final X defaultValue;

    Primitive(Class boxClass, X defaultValue) {
       this.boxClass = boxClass;
       this.defaultValue = defaultValue;
    }
}

К сожалению, эксперименты этого усовершенствования внутри приложения компилятора Java доказали, что оно менее жизнеспособно, чем считалось ранее . Добавление общей информации о типе к элементам enum сделало невозможным затем использовать эти enums в качестве общих типов на других классах – например, EnumSet . Это резко снижает полезность повышения.

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

6. Резюме

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