2020 уже в разгаре, давайте же обсудим, какие изменения в мире Java нас ожидают в этом году. В этой статье перечислю основные тренды Java и JDK. И буду рад дополнениям от читателей в комментариях.
Сразу оговорюсь, что статья носит скорее ознакомительный характер. Детали по каждой рассмотренной теме можно найти на сайте соответствующего проекта или в публикациях в открытых источниках.
Итак, начнем. К сожалению, сразу придется разочаровать тех, кто не слишком следит за циклом Java-релизов, но ждет длинную программу поддержки (LTS). В этом году нас ждут релизы только с коротким жизненным циклом поддержки (STS).
Первым мы рассмотрим грядущий релиз JDK 14, который должен выйти уже в середине марта. В этом релизном цикле заявлено целых 16 JEP-ов. Вот полный список:
Многие JEP-ы из этого списка широко освещались на конференции Joker 2019. Остановлюсь на тех, которые представляются мне наиболее интересными.
Долгий JEP наконец-то выходит в Preview. Думаю, если вы практикующий программист, который промышленно пишет код на Java много лет, то не раз сталкивались с этой болью:
Если же вы писали или пишете код еще и на Kotlin, то боль от увиденного кода на Java немного усиливается. Участники проекта Amber представят нам своё видение Pattern Matching в Java, которое должно уменьшить эту боль. С приходом Java 14 мы сможем переписать пример следующим образом:
Кажется, что дополнение не такое уж и ценное – экономим строчку кода. Но представим, что мы хотим сделать следующее:
Громоздко выглядит, не так ли? Попробуем тоже самое, но с Pattern Matching.
Так явно будет лучше. Но помним, что статус у этой функциональности – Preview. Посмотрим, что изменится со временем. Что касается меня, это точно сделает мою жизнь лучше.
На дворе 2020 год, а вы еще пишете так, что у вас вылетают NullPointerExceptions? Не переживайте, вероятно, вы не единственный. Goetz Lindenmaier и Ralf Schmelter не предложили новый способ уйти от NullPointerExceptions (Optional все еще с нами), но предложили улучшить процесс отладки приложения, чтобы понять, где же именно лежит null. Итак, представим, что мы пишем код в пятом часу… ночи, конечно же. И написали мы вот такую функцию:
Неплохо, но совсем позабыли поставить аннотации @Nullable и @Nonnull и проверить наличие адреса в переданных полях. Получили NullPointerException. Что говорит нам исключение?
Увы, видим только строку, класс и стек. В каком именно месте вернулся null? Может, это request? Может, getAddress() вернул null? А может, и getStreet()? Что ж, цепочки вызовов иногда приносят боль.
Авторы JEP предлагают решение: при формировании исключения предполагается обойти стек, чтобы определить место, где именно вернулся null, а затем вывести название переменных/методов. Попробуем теперь Java 14 с опцией -XX:+ShowCodeDetailsInExceptionMessages. Запускаем и получаем несколько иное:
Теперь мы знаем, что ночное программирование до добра не доводит (но доводит порой все-таки до выполнения задач в срок), а в нашей программе мы забыли, что адрес не является обязательным полем.
Все еще генерируете getters/setters/equals/hashCode с помощью idea? Тогда этот JEP идёт к вам!
Data-классы занимают в жизни разработчика прикладного программного обеспечения далеко не последнее место. Каждый раз нам приходится или генерировать методы Data-классов с помощью нашей любимой IDE, или использовать различные compile-time плагины для генерации нужных методов, таких как lombok.
В итоге у нас очень много кода, похожего на этот:
Или такого:
В Java 14 участники проекта Amber предлагают новый синтаксис для создания дата-классов. Для этого нужно использовать новое ключевое слово record. Синтаксис для Record немного иной, чем для описания классов или enum, и слегка похож на Kotlin. Код, приведенный выше, можно переписать следующим образом:
Все поля записей по умолчанию имеют модификаторы private и final. Сам рекорд является final-классом и не может наследоваться от другого класса, но может реализовывать интерфейсы. В рекорд-классах из коробки мы получаем: getters-методы, публичный конструктор, параметры которого являются все поля record в порядке описания, equals/hashCode и toString. Из неприятного: мы не можем добавить в record поля, кроме тех, которые указаны после названия класса. Например, этот код приведет к ошибке:
Текстовые блоки были выпущены как превью еще в Java 13. Мы, пользователи, покрутили, повертели и дали feedback (я искренне верю, что вы уже используете Java 13 с preview). В результате создатели Java внесли небольшие изменения в textblocks. Во-первых, теперь мы можем явно указывать место окончания строки, поставив escapesequence\s в нашу строку. Вот пример:
Теперь мы явно задали все пробелы до символа \s и все символы пробела будут сохранены до символа \s.
Во-вторых, теперь мы можем переносить длинные строки текстового блока, не получая в итоговую строку \n символов. Для этого мы должны только добавить \ в месте переноса строки. Как это выглядит:
После выполнения мы получим следующую строку: “This is the long text block with escape string that is really well-done functional”.
Неплохое, как мне кажется, дополнение. Я очень жду перевода этого функционала в standard.
Все функции, которые мы рассмотрели, наверняка будут широко обсуждаться на ближайших конференциях. Некоторые из них уже обсуждались на Joker 2019. Обязательно посмотрите доклад Joker 2019 на эту тему “Feature evolution in Java 13 and beyond” от Cay Horstmann.
В JEP-списке есть два интересных пункта в инкубаторе. Для начала, у нас появится универсальный инструмент, который будет создавать инсталляторы для разных ОС (ну наконец-то, хочется сказать тем, кто танцевал вокруг установки программ на Windows). Jpacker будет способен создавать инсталляторы msi/exe для Windows, пакеты для macOS и rpm/ deb для Linux. Посмотрим, что из этого выйдет, но в тех редких случаях, когда я делал что-то для desktop, я лично страдал от того, что у меня не было штатного инструмента для сборки инсталлятора.
Еще более перспективным выглядит новый API для доступа к “Foreign-Memory”, т.е. всякого рода нативной или персистентной памяти. В первую очередь этот API пригодится создателям баз данных на Java или создателям фреймворков, таких, как, например, Netty. Именно они, используя Unsafe и ByteBuffer, максимально оптимизируют доступ к памяти с off-heap.
В сентябре нас ждет очередной short-term support release под номером 15. Список JEP, которые войдут в окончательный релиз, еще открыт. Пока можно видеть очень много различных изменений в самом языке, в стандартной библиотеке и виртуальной машине.
Вот список кандидатов (может быстро меняться, актуально смотрим тут: bugs.openjdk.java.net/secure/Dashboard.jspa?selectPageId=19114):
Как можно видеть, в списке по-прежнему нет сильно ожидаемых вещей. В первую очередь для меня это Project Loom. Идея структурного параллелизма очень популярна в последние годы. Сопрограммы позволяют сильно упростить задачу конкурентных параллельных вычислений и асинхронное исполнение задач. Отличные примеры реализации этой идеи можно увидеть, например, в языках Kotlin (coroutines) и Go (goroutines). В Java также прорабатывают идею структурного параллелизма, и уже есть первые результаты. Пока их можно посмотреть, лишь собрав JDK из репозитория проекта.
Очень перспективный проект Valhalla также пока не порадовал нас каким-либо превью. Интересный доклад по этому проекту был представлен на Joker 2019 (“Нужны ли в Java «инлайн»-типы? Узкий взгляд инженера по производительности на проект Valhalla” от Сергея Куксенко).
Что же представлено в списке?
Первое, что бросается в глаза – это JSON API. Сразу напрашивается вопрос – а зачем? Однозначного ответа явно нет. В JEP-секции о мотивации сказано, что JSON стал чем-то типа стандарта для web-сервисов, и вот оно время, чтобы адаптировать Java SE для взаимодействия с JSON (даже несмотря на то, что сейчас есть тонна библиотек для парсинга JSON). Самое вероятное объяснение – возможность разработчикам ПО использовать небольшой core API, чтобы снизить размер bundle, не затаскивая тяжелый Jackson себе. Иного объяснения я не вижу, поскольку в нем даже не будет databinding.
Также мы видим ряд улучшений, связанных с криптографическим API. Для начала, разработчики JDK хотят внести расширение в процесс валидации SSL-сертификатов, добавив поддержку EVSSL-сертификатов. С помощью этого API в Java можно будет определить, является ли SSL-соединение доверенным по сертификату EV (Extended Validation). Будет полностью поддержан EVSSL-сертификат в соответствии с guideline. Также будет добавлен новый криптографический алгоритм EdDSA и будет улучшена проверка HSM-криптографии.
Из языковых вещей я бы выделил реализацию Generics над примитивами. Все, кто когда-либо программировал на C# и перешел на Java, наверняка могли задать вопрос, почему же нельзя делать Generic-типы на примитивах. Ответ прост – Generics работают только с объектами, а примитивы не являются объектами и, в частности, не наследуют Object-класс. Уже не первый год идет война по этому вопросу, и Brian Goetz снова возвращается к нему. Описывать особо пока нечего. Задача понятна – поддержать такие конструкции, как List. Но даже на текущий момент есть 13 открытых вопросов, которые надо решить перед внедрением этого функционала. Честно говоря, мне интересно, чем закончится этот сериал.
И последнее, что хочется затронуть, – это sealed types. Это очередной шаг к pattern matching. Sealed Types – это расширение языка, которое внедряет ключевые слова sealed (модификатор) и permits для класса или интерфейса.
С помощью sealed-класса мы ограничиваем число наследников только теми классами, которые указаны в permits (явное ограничение) или в той же единице компиляции (файле). Пример описания sealed class:
Sealed-модификатор гарантирует, что только ограниченный конечный набор наследников может расширять базовый класс или реализовывать интерфейс. Это свойство можно использовать при обработки объектов данных классов. И, конечно же, это отличный кандидат на использование в switch-операторе с pattern matching.
Мы посмотрели на разные нововведения JDK этого года. Какие-то из них выстрелят, какие-то нет. Но больше всего в новых JDK я жду новые маленькие (или не очень) оптимизации, которые бесплатно делают наши программы быстрее с каждым релизом. И если вы были на последнем Joker 2019 и посетили доклад Тагира Валеева Java 9-14: Маленькие оптимизации, то, скорее всего, также как и я, были впечатлены той работой, которую проделывают контрибьюторы для оптимизации JDK. Ее результаты не видны с первого взгляда и не отражены не в одном JEP, но мы используем их каждый день.
Хороших выпусков Java нам всем. Изучайте новые возможности платформы, ходите на конференции и следите за трендами.
Сразу оговорюсь, что статья носит скорее ознакомительный характер. Детали по каждой рассмотренной теме можно найти на сайте соответствующего проекта или в публикациях в открытых источниках.
Итак, начнем. К сожалению, сразу придется разочаровать тех, кто не слишком следит за циклом Java-релизов, но ждет длинную программу поддержки (LTS). В этом году нас ждут релизы только с коротким жизненным циклом поддержки (STS).
Первым мы рассмотрим грядущий релиз JDK 14, который должен выйти уже в середине марта. В этом релизном цикле заявлено целых 16 JEP-ов. Вот полный список:
305: | Pattern Matching for instanceof (Preview) |
343: | Packaging Tool (Incubator) |
345: | NUMA-Aware Memory Allocation for G1 |
349: | JFR Event Streaming |
352: | Non-Volatile Mapped Byte Buffers |
358: | Helpful NullPointerExceptions |
359: | Records (Preview) |
361: | Switch Expressions (Standard) |
362: | Deprecate the Solaris and SPARC Ports |
363: | Remove the Concurrent Mark Sweep (CMS) Garbage Collector |
364: | ZGC on macOS |
365: | ZGC on Windows |
366: | Deprecate the ParallelScavenge + SerialOld GC Combination |
367: | Remove the Pack200 Tools and API |
368: | Text Blocks (Second Preview) |
370: | Foreign-Memory Access API (Incubator) |
Многие JEP-ы из этого списка широко освещались на конференции Joker 2019. Остановлюсь на тех, которые представляются мне наиболее интересными.
Pattern Matching for instanceof (Preview)
Долгий JEP наконец-то выходит в Preview. Думаю, если вы практикующий программист, который промышленно пишет код на Java много лет, то не раз сталкивались с этой болью:
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.toUpperCase());
}
Если же вы писали или пишете код еще и на Kotlin, то боль от увиденного кода на Java немного усиливается. Участники проекта Amber представят нам своё видение Pattern Matching в Java, которое должно уменьшить эту боль. С приходом Java 14 мы сможем переписать пример следующим образом:
if (obj instanceof String s) {
System.out.println(s.toUpperCase());
}
Кажется, что дополнение не такое уж и ценное – экономим строчку кода. Но представим, что мы хотим сделать следующее:
if (obj instanceof String) {
String s = (String) obj;
if (s.contains(“prefix_”)) {
return s.toUpperCase();
}
}
return null;
Громоздко выглядит, не так ли? Попробуем тоже самое, но с Pattern Matching.
return (obj instanceof String s) && s.contains(“prefix_”) ? s.toUpperCase() : null;
Так явно будет лучше. Но помним, что статус у этой функциональности – Preview. Посмотрим, что изменится со временем. Что касается меня, это точно сделает мою жизнь лучше.
Helpful NullPointerExceptions
На дворе 2020 год, а вы еще пишете так, что у вас вылетают NullPointerExceptions? Не переживайте, вероятно, вы не единственный. Goetz Lindenmaier и Ralf Schmelter не предложили новый способ уйти от NullPointerExceptions (Optional все еще с нами), но предложили улучшить процесс отладки приложения, чтобы понять, где же именно лежит null. Итак, представим, что мы пишем код в пятом часу… ночи, конечно же. И написали мы вот такую функцию:
public String getStreetFromRequest(Request request) {
return request.getAddress().getStreet().toUpperCase();
}
Неплохо, но совсем позабыли поставить аннотации @Nullable и @Nonnull и проверить наличие адреса в переданных полях. Получили NullPointerException. Что говорит нам исключение?
Exception in thread "main" java.lang.NullPointerException
at Program.getStreetFromRequest(Program.java:10)
at Program.main(Program.java:6)
Увы, видим только строку, класс и стек. В каком именно месте вернулся null? Может, это request? Может, getAddress() вернул null? А может, и getStreet()? Что ж, цепочки вызовов иногда приносят боль.
Авторы JEP предлагают решение: при формировании исключения предполагается обойти стек, чтобы определить место, где именно вернулся null, а затем вывести название переменных/методов. Попробуем теперь Java 14 с опцией -XX:+ShowCodeDetailsInExceptionMessages. Запускаем и получаем несколько иное:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toUpperCase()" because the return value of "Address.getStreet()" is null
at Program.getStreetFromRequest(Program.java:10)
at Program.main(Program.java:6)
Теперь мы знаем, что ночное программирование до добра не доводит (но доводит порой все-таки до выполнения задач в срок), а в нашей программе мы забыли, что адрес не является обязательным полем.
Records (Preview)
Все еще генерируете getters/setters/equals/hashCode с помощью idea? Тогда этот JEP идёт к вам!
Data-классы занимают в жизни разработчика прикладного программного обеспечения далеко не последнее место. Каждый раз нам приходится или генерировать методы Data-классов с помощью нашей любимой IDE, или использовать различные compile-time плагины для генерации нужных методов, таких как lombok.
В итоге у нас очень много кода, похожего на этот:
public class Request {
private Address address;
public Address getAddress() {
return address;
}
public void setAddress(Address address) {
this.address = address;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Request request = (Request) o;
return Objects.equals(address, request.address);
}
@Override
public int hashCode() {
return Objects.hash(address);
}
@Override
public String toString() {
return "Request{" +
"address=" + address +
'}';
}
}
Или такого:
@Data
@AllArgsConstructor
public class Request {
private Address address;
}
В Java 14 участники проекта Amber предлагают новый синтаксис для создания дата-классов. Для этого нужно использовать новое ключевое слово record. Синтаксис для Record немного иной, чем для описания классов или enum, и слегка похож на Kotlin. Код, приведенный выше, можно переписать следующим образом:
public record Request(Address address) {
}
Все поля записей по умолчанию имеют модификаторы private и final. Сам рекорд является final-классом и не может наследоваться от другого класса, но может реализовывать интерфейсы. В рекорд-классах из коробки мы получаем: getters-методы, публичный конструктор, параметры которого являются все поля record в порядке описания, equals/hashCode и toString. Из неприятного: мы не можем добавить в record поля, кроме тех, которые указаны после названия класса. Например, этот код приведет к ошибке:
public record Request(Address address) {
private final String anotherParameter; // compilation error
}
Text Blocks (Second Preview)
Текстовые блоки были выпущены как превью еще в Java 13. Мы, пользователи, покрутили, повертели и дали feedback (я искренне верю, что вы уже используете Java 13 с preview). В результате создатели Java внесли небольшие изменения в textblocks. Во-первых, теперь мы можем явно указывать место окончания строки, поставив escapesequence\s в нашу строку. Вот пример:
public static void main(String[] args) {
final var test = """
This is the long text block with escape string \s
that is really well done \s
""";
System.out.println(test);
}
Теперь мы явно задали все пробелы до символа \s и все символы пробела будут сохранены до символа \s.
Во-вторых, теперь мы можем переносить длинные строки текстового блока, не получая в итоговую строку \n символов. Для этого мы должны только добавить \ в месте переноса строки. Как это выглядит:
public static void main(String[] args) {
final var test = """
This is the long text block with escape string \
that is really well-done functional
""";
System.out.println(test);
После выполнения мы получим следующую строку: “This is the long text block with escape string that is really well-done functional”.
Неплохое, как мне кажется, дополнение. Я очень жду перевода этого функционала в standard.
Все функции, которые мы рассмотрели, наверняка будут широко обсуждаться на ближайших конференциях. Некоторые из них уже обсуждались на Joker 2019. Обязательно посмотрите доклад Joker 2019 на эту тему “Feature evolution in Java 13 and beyond” от Cay Horstmann.
И еще немного интересных вещей
В JEP-списке есть два интересных пункта в инкубаторе. Для начала, у нас появится универсальный инструмент, который будет создавать инсталляторы для разных ОС (ну наконец-то, хочется сказать тем, кто танцевал вокруг установки программ на Windows). Jpacker будет способен создавать инсталляторы msi/exe для Windows, пакеты для macOS и rpm/ deb для Linux. Посмотрим, что из этого выйдет, но в тех редких случаях, когда я делал что-то для desktop, я лично страдал от того, что у меня не было штатного инструмента для сборки инсталлятора.
Еще более перспективным выглядит новый API для доступа к “Foreign-Memory”, т.е. всякого рода нативной или персистентной памяти. В первую очередь этот API пригодится создателям баз данных на Java или создателям фреймворков, таких, как, например, Netty. Именно они, используя Unsafe и ByteBuffer, максимально оптимизируют доступ к памяти с off-heap.
Следующий релиз. Радость и разочарование
В сентябре нас ждет очередной short-term support release под номером 15. Список JEP, которые войдут в окончательный релиз, еще открыт. Пока можно видеть очень много различных изменений в самом языке, в стандартной библиотеке и виртуальной машине.
Вот список кандидатов (может быстро меняться, актуально смотрим тут: bugs.openjdk.java.net/secure/Dashboard.jspa?selectPageId=19114):
111: | Additional Unicode Constructs for Regular Expressions |
116: | Extended Validation SSL Certificates |
134: | Intuitive Semantics for Nested Reference Objects |
152: | Crypto Operations with Network HSMs |
198: | Light-Weight JSON API |
218: | Generics over Primitive Types |
300: | Augment Use-Site Variance with Declaration-Site Defaults |
301: | Enhanced Enums |
302: | Lambda Leftovers |
303: | Intrinsics for the LDC and INVOKEDYNAMIC Instructions |
306: | Restore Always-Strict Floating-Point Semantics |
338: | Vector API (Incubator) |
339: | Compiler Intrinsics for Java SE APIs |
348: | Compiler Intrinsics for Java SE APIs |
356: | Enhanced Pseudo-Random Number Generators |
360: | Sealed Types (Preview) |
371: | Hidden Classes |
Как можно видеть, в списке по-прежнему нет сильно ожидаемых вещей. В первую очередь для меня это Project Loom. Идея структурного параллелизма очень популярна в последние годы. Сопрограммы позволяют сильно упростить задачу конкурентных параллельных вычислений и асинхронное исполнение задач. Отличные примеры реализации этой идеи можно увидеть, например, в языках Kotlin (coroutines) и Go (goroutines). В Java также прорабатывают идею структурного параллелизма, и уже есть первые результаты. Пока их можно посмотреть, лишь собрав JDK из репозитория проекта.
Очень перспективный проект Valhalla также пока не порадовал нас каким-либо превью. Интересный доклад по этому проекту был представлен на Joker 2019 (“Нужны ли в Java «инлайн»-типы? Узкий взгляд инженера по производительности на проект Valhalla” от Сергея Куксенко).
Что же представлено в списке?
Первое, что бросается в глаза – это JSON API. Сразу напрашивается вопрос – а зачем? Однозначного ответа явно нет. В JEP-секции о мотивации сказано, что JSON стал чем-то типа стандарта для web-сервисов, и вот оно время, чтобы адаптировать Java SE для взаимодействия с JSON (даже несмотря на то, что сейчас есть тонна библиотек для парсинга JSON). Самое вероятное объяснение – возможность разработчикам ПО использовать небольшой core API, чтобы снизить размер bundle, не затаскивая тяжелый Jackson себе. Иного объяснения я не вижу, поскольку в нем даже не будет databinding.
Также мы видим ряд улучшений, связанных с криптографическим API. Для начала, разработчики JDK хотят внести расширение в процесс валидации SSL-сертификатов, добавив поддержку EVSSL-сертификатов. С помощью этого API в Java можно будет определить, является ли SSL-соединение доверенным по сертификату EV (Extended Validation). Будет полностью поддержан EVSSL-сертификат в соответствии с guideline. Также будет добавлен новый криптографический алгоритм EdDSA и будет улучшена проверка HSM-криптографии.
Из языковых вещей я бы выделил реализацию Generics над примитивами. Все, кто когда-либо программировал на C# и перешел на Java, наверняка могли задать вопрос, почему же нельзя делать Generic-типы на примитивах. Ответ прост – Generics работают только с объектами, а примитивы не являются объектами и, в частности, не наследуют Object-класс. Уже не первый год идет война по этому вопросу, и Brian Goetz снова возвращается к нему. Описывать особо пока нечего. Задача понятна – поддержать такие конструкции, как List. Но даже на текущий момент есть 13 открытых вопросов, которые надо решить перед внедрением этого функционала. Честно говоря, мне интересно, чем закончится этот сериал.
И последнее, что хочется затронуть, – это sealed types. Это очередной шаг к pattern matching. Sealed Types – это расширение языка, которое внедряет ключевые слова sealed (модификатор) и permits для класса или интерфейса.
С помощью sealed-класса мы ограничиваем число наследников только теми классами, которые указаны в permits (явное ограничение) или в той же единице компиляции (файле). Пример описания sealed class:
// неявно
public abstract sealed class Base {
public static class ChildA extends Base{}
public static class ChildB extends Base{}
}
// явно
public sealed interface BaseInterface permits ChildC, ChildD{
}
//в другом файле
public class ChildC implements BaseInterface {
}
//в другом файле
public class ChildD implements BaseInterface {
}
Sealed-модификатор гарантирует, что только ограниченный конечный набор наследников может расширять базовый класс или реализовывать интерфейс. Это свойство можно использовать при обработки объектов данных классов. И, конечно же, это отличный кандидат на использование в switch-операторе с pattern matching.
Заключение
Мы посмотрели на разные нововведения JDK этого года. Какие-то из них выстрелят, какие-то нет. Но больше всего в новых JDK я жду новые маленькие (или не очень) оптимизации, которые бесплатно делают наши программы быстрее с каждым релизом. И если вы были на последнем Joker 2019 и посетили доклад Тагира Валеева Java 9-14: Маленькие оптимизации, то, скорее всего, также как и я, были впечатлены той работой, которую проделывают контрибьюторы для оптимизации JDK. Ее результаты не видны с первого взгляда и не отражены не в одном JEP, но мы используем их каждый день.
Хороших выпусков Java нам всем. Изучайте новые возможности платформы, ходите на конференции и следите за трендами.