Java 23: Quo vadis?
Усі ми любимо говорити й мріяти про майбутнє. Плануємо, передбачаємо, вибудовуємо стратегії, але часто розчаровуємося тоді, коли нашим планам не судилося втілитись. Це все природно, бо нікому з нас майбутнє невідоме достеменно — ми можемо лише здогадуватися.
15 лютого відбувся JavaDay Lviv 2020, на якому в межах BoF (для тих, хто не в курсі, — Birds of a feather) я разом з учасниками конференції спробував передбачити, якою ж буде Java 2024 року (бо саме тоді вийде LTS Java 23). Отож, що, ймовірно, потрапить до оновленої версії, а що — ні, читайте далі.
Як побудовано цей прогноз
Мій улюблений науковець-футуролог Мічіо Каку у своїй книжці «Фізика майбутнього» дуже цікаво описує процес передбачення майбутнього, а точніше те, як зробити так, щоб помилятися якомога менше.
Згідно зі спостереженнями Мічіо, коли робиш передбачення на найближчих кілька років, не варто сподіватися на різкі зміни чи перевороти, — ти й так їх не помітиш. Така вже людська природа сприйняття змін. Ми не усвідомлюємо їхньої значущості в короткому часовому відрізку, особливо, якщо самі перебуваємо в ньому. Проте в далекій перспективі, скажімо сторічній, усе здаватиметься кардинально іншим.
Такий самий підхід, на мою думку, можна застосовувати до прогнозування майбутнього Java. Згадайте, якою Java була до дженериків? Як узагалі без них можна програмувати (варто запитати про це на Go-конференції)?
З Java, правда, усе водночас простіше та складніше через її open-source-природу. Щоб ті або інші зміни були втілені в життя, вони повинні бути зреалізовані через Java Enhancement Proposal (JEP), який, до речі, суттєво полегшив життя комітерам, як порівняти з Java Specification Request (який формально все ще потрібний). Саме ці JEP-и й будуть основним вказівником, на який я орієнтуватимуся у своїх передбаченнях.
Браян Гетц (Java Platform Architect @ Oracle) в одному зі своїх виступів, присвячених JEP-359 Records (що як preview feature з’явилися в Java 14), дуже чітко сформулював, що ж потрібно, щоб мова/платформа залишалася релевантною й бажаною для її користувачів у тривалій перспективі. Він виокремив такі умови:
- підходить, щоб вирішувати на ній актуальні задачі;
- ефективно працює на сучасному залізі;
- розуміє (щораз більші) потреби користувачів;
- дотримується обіцянок.
Тому, якщо взяти до уваги ці умови, доповнити їх даними про загальний напрямок руху платформи (JEP-и й Project-и, уже частково зреалізовані або в активній стадії розробки), а також знати загальні тренди індустрії, то можна спробувати створити доволі правдоподібну картину майбутнього Java.
Проте для зручності й точності нашого передбачення об’єднаймо потенційні зміни в три категорії:
- ті, що потрапляють у Java 23;
- ті, що можуть потрапити в Java 23;
- ті, що, найімовірніше, не потраплять у Java 23 (а хотілось би).
Disclaimer. Перераховувати все, що потрапить, а тим паче те, що не потрапить, задача — цікава, але, на мій погляд, малоефективна. Я ж спробую коротко дати уявлення про такі речі, які, знову ж таки, на мій погляд, будуть game changer і матимуть найбільший вплив на Java та на JVM як платформу.
Потрапить
Усе добре, що добре завершується. Гадаю, саме таким постулатом керуватимуться Браян Гетц, Марк Ренхольд і товариство: постараються найперше завершити Project Amber. А залишилося зовсім нічого — два JEP-и 348 і 360.
Щоб завершити епопею pattern matching і make switch great again, бракує останнього штриха — sealed types (JEP-360).
Sealed-конструкції покликані виконувати дві функції: додати ще один рівень фінальності в класи (згенералізувати фінальність, якщо можна так сказати) й бути джерелом виключеності. Це матиме десь отакий вигляд:
Тут ми маємо модифікатор sealed, який свідчить, що певний клас Expression можна екстенднути лише переліком класів (насправді final class стає наступним ступенем фінальності після sealed, оскільки не дозволяє розширення зовсім). Інший модифікатор — permits — опційний і якраз вказує перелік дозволених субтипів через кому (такі дозволені типи повинні бути членами одного пакета або модуля).
Такий, наприклад, може мати вигляд реалізація згаданого вище sealed interface за допомогою records:
Зазвичай таке оголошення буде надлишковим, тому компілятор повинен уміти з’ясовувати, які ж субтипи все-таки sealed. Усе вище сказане повинне вилитися в змогу писати такий код:
Вигляд має непоганий, тому, напевно, мають бути певні обмеження. І такі справді є: не вдасться створити анонімний підклас або лямбду sealed-типу.
Якщо з якоїсь причини вам треба розширити sealed-клас або інтерфейс, але ви не хочете, щоб саме ця конкретна реалізація була теж sealed, то допоможе ще один модифікатор non-sealed. Для рекордів такий фокус не провернути — вони завжди final.
І якщо результат JEP-360 буде видимий кожному, то JEP-348 — герой непоказний. Він покликаний зробити наш з вами код, зокрема вже написаний, швидшим.
Скільки разів ви відмовилися від String::format на користь конкатенації або StringBuilder-a задля перформансу? Гадаю, траплялося.
Імовірно, скоро нам більше не доведеться вибирати швидший, але некрасивий і з потенційними помилками код, адже JEP-348 передбачає нові стратегії генерації коду (за допомогою intrinsic-функцій) для Java SE APIs, зокрема й для класів String та Formatter.
Інтринсиків (що це таке, можна прочитати тут) у Java вже й так чимало, тому JEP пропонує створити певний абстрактний механізм, коли JIT може на свій розсуд замінити виклик методу інтринсик-функцією. Інженерові варто лише позначити цей метод, як потенційного кандидата на таку заміну (ну й написати реалізацію інтринсик-функції, звісно) через анотацію @IntrinsicCandidate.
Заодно JEP приведе до спільного знаменника назву анотації для всіх реалізацій VM, оскільки тепер використовують @HotSpotIntrinsicCandidate.
З обмежень, які накладають (куди ж без них) кандидати на інтринсифікацію, можуть бути тільки методи, які експортують з модуля java.base і є інстанс-методами в фінальному класі або статичними методами.
Завдяки інтринсикам байткод String:format може стати в
Можливо, потрапить, але це не точно
Шлях до Валгалли.
Якщо повернутися назад у минуле, десь так у рік
Який це має вигляд (дуже спрощено за допомогою Paint-a)? Є в нас певний клас Location, в якого є поля: довгота й широта.
Нічого особливого, звичайний собі клас. А тепер створімо масив з таких об’єктів. Представлення такої конструкції в пам’яті можна зобразити так:
І все б нічого, якби на сучасному залізі багата на пойнтери система не була така затратна. Так, на сучасних ЦП ціна того ж самого пойнтер-дереференсу становить ~300 додавань, а якщо cache miss, може сягати й 1000.
Чому ж так складно? Причина в object identity, що гарантує змінюваність об’єкта: щоб змінити поле об’єкта, ми повинні точно знати, який саме об’єкт змінюємо. Так само object identity підтримує поліморфізм. Ба більше, навіть якщо об’єкту чхати на змінюваність і поліморфізм, identity все ще має ключове значення під час порівнювання об’єктів (==), синхронізації, виклику System::identityHashCode тощо.
З цього випливає резонне запитання: «А чи всім моїм об’єктам потрібне identity?» Ні, далеко не всім. Часто нам абсолютно байдуже до identity об’єкта. Яскравий приклад цьому — Optional. Нас цікавить лише те, що всередині такої обгортки (або чого немає, як у випадку з Optional), тобто наші дані.
Розв’язати цю проблему й зробити Java в рази швидшою та ефективнішою покликаний Project Valhalla за допомогою inline-класів.
Inline-класи — це неоднорідні агрегати даних (як звичайні класи), які явно відмовляються від identity. У результаті вони також незмінні (immutable) і не підтримують поліморфізм. Перевагою такого купованого класу є його «плоска» репрезентація в пам’яті.
Codes like a class, works like an int. Inline-класи спокійно можна розглядати як «швидші класи», або «користувацькі примітиви».
Project Valhalla розпочали 2014 року, і нині він перебуває на етапі LW(L-World)2, пройшовши етапи MVT і LW1 (префікс L повинен допомогти зрозуміти, що inline-класи є частиною
А завантажити й спробувати ea-білд Valhalla можна тут.
Проте не Валгаллою єдиною. Коли я сказав, що Project Amber завершиться з релізом JEP 348 і 360, то дещо лукавив.
Дуже вже дивно те, що гілка в гіті є, а чернетки JEP-a (не кажучи вже про сам JEP) для такої фічі немає. Ідеться про локальні методи. Інформації обмаль, окрім самого репозиторію й треду на реддіті. Знайти чогось більше не вдалося. Тож я вирішив запитати в того, хто знає напевно.
Перший коміт у гілку відбувся в жовтні 2019 року, проте активний девелопмент ведуть тільки з середини лютого
Який вигляд матимуть локальні методи, уявити неважко, бо вони вже давно є, наприклад у Котліні.
Може стати в пригоді для складних рекурсивних функцій, що повинно полегшити їхнє читання й дебаг, але, звичайно, use case цим не обмежується.
На мій погляд, локальні методи можна чекати вже в Java 17, як preview feature, але, оскільки немає нормального джерела, звідки можна довідатися, на якому ж етапі вони перебувають, залишу їх у цьому розділі.
Не потрапить, а хотілося б
Багатопотоковість тривалий час була й залишається killer-feature для Java, яку багато хто в ній любить і, напевно, стільки ж її ненавидить. З багатопотоковістю все непросто: «хочеться і штани зняти, і хрестик надіти».
Потоки в Java — це природний і зручний інструмент, що дедалі більше витісняє асинхронний підхід, оскільки поточна native-реалізація (як OS kernel thread) не дуже відповідає сучасним потребам і доволі затратна щодо обчислювальних ресурсів, особливо, коли справа доходить до цінника в клаудах.
Та чи все з асинхронним програмуванням добре? В Інтернеті виокремлюють три основні проблеми асинхронного програмування:
- втрата контролю над фловом бізнес-логіки (звісно, як порівняти з класичною процедурно-послідовною блокувальною моделлю);
- втрата контексту (кожен колбек або future раниться у своєму незалежному треді, що спричинює абсолютну неінформативність стек-трейсів);
- заразність (якщо вже викликав метод, що повертає Future, то, будь ласкавий, Future теж повернути. Можна заблокатися, звісно, але це не те, чого ми хочемо, правда?).
Чимало мов і бібліотек стараються згладити ці проблеми. Яскравим прикладом цього є концепт async/await. Нативний для JS, імплементований на рівні мови в C# і Kotlin або на рівні бібліотеки в Scala. А що ж у Java?
Колись, у часи бурхливої юності Java (Java 1.2), були так звані зелені потоки, за менеджмент яких відповідала сама JVM. То, напевно, правду кажуть: усі нові ідеї — це добре забуті старі, Java чекає Project Loom.
Цей проєкт і покликаний повернути джаві джавове, тільки з новою назвою і під новим соусом — файбери. Надихнувшись, очевидно, успіхом котліна в адаптації корутин, які, своєю чергою, надихнулися горутинами, що, найімовірніше, надихнулися Erlang... Так, про що це я? О, так, файбери. На жаль, на мою думку, їх у Java 23 чекати не слід.
Якщо коротко, то такий легковаговий потік (таким файбер і повинен бути) повинен містити дві частини: планувальник і континуації. І якщо чудовий планувальник — ForkJoinPool уже є, то залишилося зовсім мало — додати континуації.
Інформації про Loom для допитливого розуму читача на просторах Інтернету доволі (наприклад, тут), а стаття й так виходить більшою, ніж я планував. У всякому разі, я особисто з нетерпінням чекаю, коли файбери з’являться в Java, адже це відкриває шлях до нової парадигми — Structured Concurrency.
Sums up
Замість висновків. Є ще чимало цікавих проєктів, яких я не згадав: це й Metropolis, покликаний зменшити кількість складного й з кожним днем дедалі важче підтримуваного
Мова й платформа з кожним релізом стає ліпшою, актуальнішою і швидшою. А той набір тулів і фреймворків, що з’явилися за останні
Можливо, ще ніколи не було ліпшого часу, щоб бути Java-інженером. А саму Java чекає світле майбутнє.