14 February

Чего ждать от Java в 2020 году?

DINS corporate blogOpen sourceJavaAPIООP
2020 уже в разгаре, давайте же обсудим, какие изменения в мире Java нас ожидают в этом году. В этой статье перечислю основные тренды Java и JDK. И буду рад дополнениям от читателей в комментариях.

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

image

Итак, начнем. К сожалению, сразу придется разочаровать тех, кто не слишком следит за циклом 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 нам всем. Изучайте новые возможности платформы, ходите на конференции и следите за трендами.
Tags:java14мнение2020jdk
Hubs: DINS corporate blog Open source Java API ООP
+15
8.3k 38
Comments 27
Ads