JDK 17 та її можливості

Підписуйтеся на Telegram-канал «DOU #tech», щоб не пропустити нові технічні статті.

14 вересня 2021 вийшла довгоочікувана версія JDK 17. Довгоочікувана, бо 17-та версія — поточна LTS (long-term service), яка буде підтримуватися до вересня 2026 (а розширена підтримка до 2029-го року). Дуже багато розробників (та й замовників) не хочуть переходити на короткоживучі версії, тому що їхній життєвий цикл — всього пів року, а далі знову upgrade, і так кожні півроку.

Попередня версія LTS була JDK 11, але вона вийшла ще в 2018 році, і хоча її життєвий цикл закінчиться тільки в 2023 році, ви можете перейти на JDK 17 вже зараз з трьох ключових причин:

  • Вийшов перший update JDK 17.0.1 з виправленням помилок.
  • Вийшло оновлення GraalVM 21, яке підтримує JDK 17 (якщо ви хочете генерувати наtive images).
  • Усі основні системи збирання (Maven 3.6 та Gradle 7.2) підтримують нову версію
  • У JDK 16 було нарешті роблено портування з бібліотеки glibc на musl, а це в свою чергу дозволяє використовувати мінімалістичний (5 Мб) образ Alpine Linux як базовий образ Docker контейнера без будь-яких додаткових пакетів

Крім того, як нещодавно оголосили розробники Spring платформи, нові версії Spring Framework 6 та Spring Boot 3, які вийдуть у 2022 році, використовуватимуть саме JDK 17 як основну версію Java.

Більше того, якщо у вас в проєкті JDK 8, ви також можете ризикнути і спробувати перейти на 17 версію, доповнивши свій арсенал розробника тими фічами, які додавалися з 9 до 17 версії. Єдине питання: а чи підтримують сучасні бібліотеки та фреймворки поточну версію? Це з’ясується лише після міграції.

Що дасть нам, як розробникам, перехід із 16-ї на 17-ю версію? Які її фічі ми вже можемо використати? Знайомі нам по JDK 16 sealed classes повністю, без змін, увійшли до поточної версії і оголошені стабільною фічею, без приставки preview:

 sealed interface Shape permits Square {}
 
final class Square implements Shape {}

Ну і головна вишенька — нова фіча pattern matching for switch, яка поки що проходить обкатку в режимі preview.

У JDK 14 з’явилася перша серйозна зміна в switch з самого початку існування Java — switch expressions, в яких можна надавати змінній те, що повертає switch:

 private void print(int days) {
              String text = switch (days) {
              case 0 -> "none";
              case 1 -> "one";
              default -> "many";
              };

Pattern matching for switch пропонує нові варіанти тих значень, які ми можемо помістити після case. По-перше, ми можемо вказати null (якщо у нас у switch посилальний тип):

        private void print(Integer days) {
              String text = switch (days) {
              case null -> "N/A";
              case 0 -> "none";
              case 1 -> "one";
              default -> "many";
       };

По-друге, ми можемо об’єднати default гілку з будь-якою іншою:

        String text = switch (days) {
              case null, default -> "N/A";
              case 0 -> "none";
              case 1 -> "one";
};

І по-третє, якщо нам зустрічається ось такий кострубатий код:

        private void handleEvent(Event event) {
              Object source = event.getSource();
              if(source instanceof Manager) {
                     processManager((Manager) source);
              } else if(source instanceof Developer) {
                     processDeveloper((Developer) source);
              } else {
                     log.debug("Received callback event {}", event);
              }
       }

То в JDK 16 його можна було більш витончено переписати, використовуючи pattern matching for instanceof:

       private void handleEvent(Event event) {
              Object source = event.getSource();
              if(source instanceof Manager manager) {
                     processManager(manager);
              } else if(source instanceof Developer dev) {
                     processDeveloper(dev);
              } else {
                     log.debug("Received callback event {}", event);
              }
}

А в JDK 17 цей блок коду можна ще більше спростити:

       Object source = event.getSource();
       switch (source) {
              case Manager manager -> processManager(manager);
              case Developer dev -> processDeveloper(dev);
              default -> log.debug("Received callback event {}", event);
}
 

Ви можете сказати, що в епоху generic types і боротьби за типізацію, такий код зустрічається дуже рідко, хіба що в якихось старих callbacks. Проте в Spring Data JDBC, якому не більше трьох років, я зустрічав і такий код:

Ну і крім того, не забувайте, що більшість нинішніх проєктів — legacy, і такого коду там теж із надлишком.

Pattern matching for switch — не окрема фіча, а частина проєкту Amber, який вже поступово перетікає в JDK, і ми саме завдяки йому можемо користуватися записами (records), текстовими блоками, ключовим словом var та багатьма іншими нововведеннями.

Ще одна нова функціональність — так званий Vector API, який покликаний використовувати розширення команд сучасних процесорів для швидких операцій з векторами. Поки що він має статус incubator. Vector API прийшов з відомого проєкту Panama, який включає розробку нових API для різного нативного і внутрішнього коду JDK.

А що ж проєкти Loom (легковажні потоки) та Valhalla (value objects та примітивні типи в generics)? Ці проєкти активно розробляються з 2014 року, але на жаль, якщо ми їх побачимо в JDK, то, очевидно, не раніше 21-22 версії.

Розробники JDK також активно чистять проєктний репозиторій від старого коду та непотрібних компонентів, а для початку вони оголосили їх deprecated у цьому релізі:

1) Applet API, який зараз практично марний, тому що сучасні браузери не підтримують аплети.

2) З відходом аплетів втратив свій сенс і SecurityManager, який повинен був захищати серверний код від атак зловмисників якраз через аплети. Тому Securi-tyManager, один із базових елементів Java Core, теж оголошений застарілим.

3) Експериментальний JIT/AOT компілятор (aotc), який з’явився у 9-й версії, але так і не зміг набрати популярності.

4) Видалено флаг illegal-access, який дозволяв отримувати доступ до внутрішніх компонентів JDK в обхід інкапсуляції. Тепер тільки два внутрішні пакети (sun.misc і sun.reflect) можуть бути доступними публічно, решта лише через публічний API. Це, до речі, може призвести до того, що багато бібліотек/фреймворків зажадають спеціальних патчів саме для JDK 17.

Крім того, було видалено ще одну застарілу технологію — RMI Activation. Перехід на JDK 17 вимагає лише кількох змін у скриптах збирання:

       <properties>
              <java.version>1.17</java.version>
 
       <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.8.1</version>
              <configuration>
                     <source>${java.version}</source>
                     <target>${java.version}</target>
                     <release>17</release>
              </configuration>
       </plugin>

Gradle:

   sourceCompatibility = 1.17
   targetCompatibility = 1.17

Також потрібно оновити всі базові образи, якщо ви використовуєте контейнери:

FROM gradle:7-jdk17-alpine

FROM openjdk:17-jdk-alpine

FROM tomcat:10-jdk17-openjdk-slim

Єдина несподіванка, з якою ви зіткнетеся, якщо використовували JRE (а не JDK) образи для 11-ї версії — починаючи з JDK 12, їх більше не постачають, в основному тому що зараз у складі JDK дистрибутива є утиліта jlink, яка дозволяє самим розробникам вказати, що з JDK залишити, а що видалити для використання.

У наших проектах, де є і Spring Boot, і Jakarta EE, міграція на JDK 17 відбулася без проблем. Щоправда, ми перед цим оновили більшість версій залежностей. Єдина проблема, з якою ми безпосередньо зіткнулися — несумісність поточної версії Lombok, але ця проблема була оперативно вирішена в новому релізі.

Таким чином, ніяких складнощів ця міграція не викликає, і ви можете зробити її, якщо звичайно вас цікавлять ті можливості, які криються в новій версії.

👍НравитсяПонравилось12
В избранноеВ избранном2
LinkedIn
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter
Допустимые теги: blockquote, a, pre, code, ul, ol, li, b, i, del.
Ctrl + Enter

Эх, тут бы, с 8-й джавы хотябы в 11-ую перейти...

А що (чи хто) заважає?

Отсутствие бюджета на технический долг?

вишенька — нова фіча pattern matching for switch

с# 6-7?

ключовим словом var

ого, це аналог того що було в с# 3.0?) ~2007ий

гайс, го шарп. там ці всі штучки вже підвезли ;)

Тільки свободу забули завезти і нормальну ОС.

в якому плані свободу?) така як в джаві від оракла?)
ну і вінда уже давно супер нормальна ОС, так що велкам ;)
чи ти рилі думаєш, що лярд+ людей сидить на відні і мучає себе?

в якому плані свободу?) така як в джаві від оракла?)

Типу такої — www.gnu.org/philosophy/free-sw.html

чи ти рилі думаєш, що лярд+ людей сидить на відні і мучає себе?

Йопта, так ті люди в кампуктерах взагалі не розбираються, інтернетом називають іконку на робочому столі. Їм що в магазі поставили тим і користуються.

Ну так гейОС також із залізом поставляється. Любий школьник про макбук мріє. Маркетинг звичайний. Я, просто, до того, що популярність мало що говорить про «якість» продукту.

А що ви порівнюєте? C# і .NET — пропрієтарна платформа, яку розробляє одна компанія.
Java розробляється community, зрозуміло, що там можуть не так швидко додавати нові фітчі.

ем, з 2014ого шарп опен-сорснули, якщо що
спеку по джаві енівей апрувий оракл, чи я щось пропустив?

Хіба можна скачати сирці віртуальної машини?

Круто. Не знав, що його відкрили.

Можна подумати, чим більше туди вундерфіч насують тим краще. А то вийде черговий C++ тільки з колектором.

це звучить дуже смішно, якщо врахувати, що джава йде по стопам шарпу в плані «вундерфіч»)
правда, на років 5-7 запізнюється

Я не думаю, що є таємна кімната з таємним списком фітч, звідки розробники JDK іноді беруть щось для нових версій.
У JDK входить те, чого там гостро не вистачає. Зрозуміло, що багато хто з цього вже є в сучасних мовах, тому ж Котліні або Скалі.

Ну давайте смеяться вместе:
1) что там по JMX?
2) что там по low overhead профилированию?
3) что там по low latency GC?
4) что там по io_uring?

1. є 3rd party порт під .нет. є wmi.
2. EventPipeProvider
3. нема. але тюнити gc в корі можна норм. як варіант його можна і вирубити. буде 0 latency
в джаві такого нема, да?)
4. буде в наст релізі. це лютий 2022ого

джава йде по стопам шарпу

:
1) 3d party port...
2) 3d party profiler...
3) нема (в джаві є Епсілон GC доречі для прихильноків noop GC)
4) нема, але скоро буде

То хто по чиїм стопам йде ;)

Java як мова не дуже розвивається, розвивається JVM як платформа і доволі стрімко. Ваші випади в сторону ораклу взагалі мимо — є ще Azul, IBM, RedHat які утворюють комьюніті що працює над JVM і оракл не має такого впливу як наприклад MS для .net

1. wmi це не 3rd party
2. EventPipeProvider це не 3rd party
4. якщо що, уже є імплементація, якщо це так критично. і да, це теж 3d party.
у вас якась фобія 3rd party?

і якщо ми говоримо про фреймворк, то в .неті є теж багато аналогів, яких нема в jvm.
+перечитайте мій перший меседж, якщо загубили контекст ;)

В скале тож все єто давно есть (и лучче), кому надо біло на джвм именно.

в f# теж багато чого є
тут ж конкретно шарп vs джава)

В блоке

Object source = event.getSource();
switch (source) {
case Manager manager -> processManager(manager);
case Developer dev -> processDeveloper(dev);
default -> log.debug("Received callback event {}", event);
}

Можно заменить первые две строки на

switch (event.getSource()) {
...
}

Будет еще лаконичнее

Хотя if — then по прежнему более читабелен. Проблема в непроизносимости лексемы «->», case — then читалось бы куда легче.

По поводу switch (event.getSource()) — увы, менее читабельный вариант. Хотя не настолько, чтобы от него отказываться, но в случае ошибки самого event — затрудняет её поиск. Кроме того, иногда этот самый критерий нужно применить и внутри кода, а значит будешь повторно дёргать getSource()? Не кошерно.

case — then читалось бы куда легче

Со времён введения лямбд в джаве все уже привыкли, так что это не такая большая проблема.

но в случае ошибки самого event — затрудняет её поиск

В случае если event.getSource() кинет эксепшн — быдет довольно понятно что это не оператор switch её кинул

Кроме того, иногда этот самый критерий нужно применить и внутри кода

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

Привыкли ПИСАТЬ, но привыкнуть читать дерьмо невозможно. А с «быдет довольно понятно» — разве это не есть определение говнокода: мне щас всё понятно = код хороший.

Я так и сказал, что в данном случае терпимо. Но есть овердохера случаев, когда за этим «get» стоит тяжёлый сетевой запрос — и вот тут уже привычка писать в подобном стиле сыграет злую шутку. По привычке вписав это счастье в аргумент, даже не задумываешься, что его неплохо бы и в try—catch и/или try-finally обернуть, и даже тестом покрыть потом.

Як правило, в жабі проблема не стрілки читати а прочитати щось типу mySuperAppUserAccountsManagementService.getAllUserAccountsList()

Якраз оце не проблема, якщо звісно не говнокодити.
App.UserManagement.getAllUsers();
Хоча звісно ж і це говнокод, і годиться лише якщо юзерів гарантовано мало, і це адмінка по керуванню ними або пересилання чогось між ними. Жодної іншої причини отримувати повний список не бачу.

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

То бюрократизм. Лайно можна написати на чому завгодно, навіть українською. Не віриш — відкрий перший ліпший проект закону :)

Все так, але от на інших мовах я такої багатослівності на пару порядків менше бачив. Мені здається, це культурна особливість спільноти, правда, я так і не зрозумів звідки вона взялася.

Із Майкрософта це взялося. Спробуй в ворді зробити веб-сторінку, почитай, прозрієш.

А от слово my в іменуваннях — особисто я б карав за це плітьми на майдані. Бо треба бути конченими мудаками, щоб таке писати в чомусь серйознішому за домашнє завдання в 9 класі. Добре що не foo, bar

А от слово my в іменуваннях

Не согласен. У меня например есть метод
Node.IsMyParent(node)

Очень классно читается в коде

Ну, тут my относится к Node, то есть это смысловое слово. А не просто говорит, что это творение афтора типа myconneksnkbazedannyh

our переводится как наш.
Нода она в единственном числе, поэтому my.
Переводится как «ЭтоМойРодитель ?» и возвращает bool

ЭтоНашРодитель.
наш это чей ? "Моя сестра носить это"©
понапридумывают, поди потом ваш говнокод почитай

Switch syntax + pattern matching — реально революційна фіча, що робить код читабельним, роблячи чернову роботу під капотом. Варто лише протестити, що саме писатиме в лог у разі помилок в написанні коду, але то вже можна в процесі.

Лише одна ця фіча вже варта переведення довготермінових проектів на нову JDK.

Нэ. Практика показала, що топова фіча 17-ки це рекорди. Їх в 10 раз більше чим switch + pattern matching

Java records не мають прямого відношення до JDK 17. Вони з’явилися в 14-й версії як preview feature і перейшли в розряд стабільної версії в JDK 16.

Ну як це не мають. 17-ка це перша LTS з рекордами. Не думаю, що хтось ранить продакшени на нон-лтс версіях. Це було б дуже прикрим самогубством.

А що за практика їх застосування? Я взагалі не розумію цього маразму, як на мене лише спрощення синтаксису. Яке давно треба було зробити. Але чим відрізняються дибільні гетери-сетери від прямого паблік доступу — мені не зрозуміти. Якщо потрібні тупо класи даних, то краще було б мати 1 тип Record, який був би тупо реалізацією інтерфейса Map.

Усюди де читав, якісь незрозумілі гіпотетичні приклади, які аж ніяк не додають читабельності коду. І в чому принципова різниця між класом та записом, поясніть хтось. Якщо його все одно треба декларувати — то чим він кращий за усе що було до того?

Я не агітую проти, просто хотілося б зрозуміти, нащо то все.

По-перше, такі типи як Java records давно є в інших мовах, наприклад data classes в Kotlin, і ніхто на це особливо не скаржиться.
Мені здається, що їхня головна фішка — не генеровані accessors (так, в записах так називаються гетери), а незмінність (immutability) нового типу.
Всі поля private final, тому до речі тут немає сеттерів (це я коментую ваше питання про публічні поля), сам record так само є final (його не можна успадкувати), і автоматично перевизначаються toString/equals/hashCode.
Тобто як Streams API в Java 8 привчав розробників до функціонального програмування, так records в Java 14 привчать їх до безпечного програмування.

їхня головна фішка — незмінність (immutability) нового типу.

Незрозуміло, що то дає насправді. Незмінність лише спрощує читання, дозволяючи побачити структуру. Так само, як і клас. Це ж не від інопланетян захист, і навіть не від дурня.

Всі поля private final, тому до речі тут немає сеттерів (це я коментую ваше питання про публічні поля), сам record так само є final (його не можна успадкувати), і автоматично перевизначаються toString/equals/hashCode.

Тобто, їх можна лише через конструктор створити вже зі значеннями? Ховайся в жито. Які в тебе шанси не помилитися при виклику конструктора із 30 параметрами?

Олексію, ну ви ж досвідчений програміст, ви ж чудово знаєте, що у іммутабільності купа плюсів:
1) Такі об’єкти безпечно використовувати у багатопоточній роботі
2) Якщо їх стан не змінюється, вони більш передбачувані (відсутні side-effects) і в тестах не потрібно перевіряти стан об’єкта після якоїсь операції
3) Такий об’єкт можна безпечно передавати до будь-якого іншого компонента, не боячись, що хтось випадково змінить його стан

Тобто, їх можна лише через конструктор створити вже зі значеннями? Ховайся в жито. Які в тебе шанси не помилитися при виклику конструктора із 30 параметрами?

У вас часто бувають класи із 30 полями?
Якщо через один, то швидше за все у вас є порушення SRP і необхідно рефакторити, виділяти нові класи і т.д.

У вас часто бувають класи із 30 полями?

Не рідкість. Навіть 8 параметрів для функції — то занадто, особливо зважаючи що їх типи не унікальні, а параметри в Java не можна іменувати на виклику.

Іменованих параметрів у Java дуууууже не вистачає. Вони роблять код читабельним, тим самим ліквідують досить серйозне джерело важко відстежуваних помилок. Особисто я не розумію, що завадило їх створити, тоді як в інших мовах для JVM вони є.

Тобто як Streams API в Java 8 привчав розробників до функціонального програмування

Ага, до маразму, зробивши замість синтаксичного цукру коротких записів та зручного механізму паралелити обчислення — тупо манію пхати це стрім апі усюди, в результаті чого читабельність коду наближується до (не)читабельності складних regex. Про можливість таке адекватно покрити тестами навіть не йдеться.

Так вас ніхто не змушує використовувати Streams API.
Пишіть через for або for-each, так кажуть, навіть швидше працювати буде.

А читати на жаль вимушений.

навіть швидше працювати буде

І це таки аргумент, бо такого коду дуже багато. Тому так, я надаю перевагу «if», а не «filter».

В чем ее революция? в ненависной тобой скале она уже 15 лет как.

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

легковажні потоки — це ті, що постійно гублять дані?

Fibers — легковажні чи віртуальні потоки, які ви можете запускати мільйонами на одній JVM.

легкомысленные потоки ?

Слово «легковажні» в українській значить дещо інше :)

наtive images — звідки стільки хейту?

А есть какие-то реальные примеры использования sealed классов?

Sealed classes тільки-но з’явилися в Java, тому не думаю, що вже хтось їх широко використовує.
Мені здається, що це швидше фітча для розробників JDK/бібліотек, ніж для коду користувача.

sealed classes — это аналог discriminated union

Это синтаксический сахар и не более. Всё это предназначено не для компилятора, а для ЧИТАТЕЛЯ кода, чтобы он видел, из каких вариантов есть выбор. Грубо говоря, это соглашение об именовании. Ничего плохого в этом нет, но и злоупотреблять не стоит.

В скале это было чуть ли ни с самого начала? Лет 10 как есть.

sealed класи дозволяють не писати (здається, обов’язковий) default в

pattern matching

, якщо перерахувати всі можливі класи, тому що компілятору вже відомо, що інших case не буде. (Мені сподобвся з цього приводу приклад www.youtube.com/watch?v=UlFFKkq6fyU на 19-й хвилині.)

Подписаться на комментарии