Długo na to czekałem! Dzisiaj bowiem znowu możemy zacząć od zestawu JEP-ów, które pewnie zobaczymy w okolicach JDK 22. Oprócz tego Pulumi, oraz ciekawe premiery. A na końcu – zaproszenie!
1. Pierwszy zestaw nowych JEP-ów po premierze JDK 21.
JEP 455: Primitive types in Patterns, instanceof, and switch (Preview)
Ten JEP jest… zaskakująco skomplikowany – ale mogłem się tego spodziewać, przecież dotyczy to typów prymitywnych, a to zawsze komplikuje. Dlatego posłużę się przykładem.
Aktualnie, użycie wzorców typów prymitywnych w Javie wiąże się z ograniczeniami. Na przykład, chociaż możemy modelować dokumenty JSON przy użyciu rekordów (jak w poniższym kodzie), mamy ograniczenie w zakresie zagnieżdżonych wzorców typów prymitywnych:
if (json instanceof JsonObject(var map)
&& map.get("age") instanceof JsonNumber(double age))
{
return new Customer(name, (int)age);
}
W powyższym fragmencie, chociaż oczekujemy, że wartość „age” będzie zawsze int
, możemy jedynie ekstrahować wartość double
i musimy polegać na manualnej konwersji (cast) do int
. Problem wynika z tego, jak JSON reprezentuje liczby oraz jak wiele bibliotek do obsługi JSON w Javie je potem interpretuje.
JSON nie rozróżnia pomiędzy różnymi typami liczbowymi. W standardzie JSON wszystkie liczby są zapisywane jako liczby zmiennoprzecinkowe. Oznacza to, że kiedy mamy wartość „age”: 25 w dokumencie JSON, ta wartość jest interpretowana jako liczba zmiennoprzecinkowa, mimo że może reprezentować wiek jako wartość całkowita.
Gdy Java odczytuje wartość z JSON, wiele bibliotek (na przykład javax.json
, org.json
itp.) interpretuje wszystkie liczby jako double
, ponieważ double
może reprezentować zarówno liczby całkowite, jak i zmiennoprzecinkowe.
Dlatego w powyższym przykładzie, kiedy próbujesz dopasować wartość „age” do wzorca JsonNumber
, wartość jest dostępna jako double
– jest najbardziej ogólny sposób reprezentowania liczb zmiennoprzecinkowych w Javie. Jeśli chcesz uzyskać wartość jako int
, musisz jawnie ją rzutować (i potencjalnie akceptować ryzyko utraty precyzji w przypadku liczb, które nie pasują dokładnie do zakresu int
).
Propozycją rozwiązania jest umożliwienie użycia wzorców typów pierwotnych w sposób bardziej uniwersalny, w taki sposób, aby były one zgodne z instanceof
oraz by były dopuszczalne w szerszej gamie przypadków. Na przykład, chcielibyśmy użyć int
bezpośrednio w wzorcu JsonNumber
, tak aby wzorzec pasował tylko wtedy, gdy wartość double
w obiekcie JsonNumber
może być konwertowana na int
bez utraty informacji:
if (json instanceof JsonObject(var map)
&& map.get("name") instanceof JsonString(String name)
&& map.get("age") instanceof JsonNumber(int age))
{
return new Customer(name, age); // bez konwersji!
}
Dzięki takiemu podejściu, można eliminować potrzebę obszernych i potencjalnie niebezpiecznych konwersji.
To tylko jeden przykład, w JEP-ie znajdziecie tego znaczenie więcej.
JEP 458: Launch Multi-File Source-Code Programs
Następny JEP to kolejne już po ułatwienie tworzenia prostych, bazowych aplikacji. Nie wiem, czy którykolwiek komercyjny programista jeszcze to pamięta, ale launcher aplikacji Java (czyli java
) od JDK umożliwia odpalanie nie skompilowanych plików .java
. Dlaczego jest to w zasadzie nieużywana metoda? Wynika to z faktu, że do tej pory należało ograniczyć się przy jej użyciu do jednego pliku. A przecież każdy dojrzały programista Javy wie, że w jednej klasie to w zasadzie co najwyżej da się wzorzec strukturalny zainicjować, a nie napisać coś cokolwiek realnie użytecznego.
Teraz zostanie rozbudowany, aby mógł uruchamiać też programy dostarczane jako wiele plików źródłowych Java, automatycznie kompilując zależności, jeśli tylko zachowują standardową strukturę pakietów. Jeśli klasa w jednym pliku źródłowym odwołuje się do klasy w innym pliku, launcher automatycznie znajduje i kompiluje ten drugi plik. Możliwe jest również korzystanie z wcześniej skompilowanych klas. Przejście z pojedynczego pliku .java
do wielu plików wymaga od programistów i uczących się Javy bardziej skomplikowanych kroków, takich jak korzystanie z javac
, także mamy kolejny ukłon w stronę nowicjuszy, jak w przypadku JEP 445: Unnamed Classes and Instance Main Methods (Preview)
JEP 457: Class-File API (Preview)
Żeby wytłumaczyć, o co chodzi w tym JEP-ie, muszę najpierw opowiedzieć o ASM. ASM to biblioteka w Javie przeznaczona do dynamicznej manipulacji i tworzenia bytecodu. Daje możliwość niskopoziomowej analizy, tworzenia oraz modyfikacji istniejących klas. Wielu twórców wykorzystuje ASM do dodawania funkcjonalności do klas, takich jak logowanie czy monitorowanie, jak również do tworzenia nowego kodu opartego na specyfikacji bytecodu. Przykłady bibliotek korzystających z ASM to AspectJ, Kryo, FindBugs czy JaCoCo, stanowi on też bazę dla popularnego (wśród twórców narzędzi) ByteBuddy’ego.
Co ciekawe, JDK (Java Development Kit) również wykorzystuje ASM w swoim wnętrzu. ASM stała się kluczową biblioteką przy implementacji pewnych funkcjonalności JDK, takich jak generowanie proxy dla lambd. W praktyce bowiem, choć JDK posiada własne narzędzia do manipulacji bytecodem, to właśnie ASM przez lata była jednym z głównych narzędzi wykorzystywanych do takich celów ze względu na swoją wydajność i elastyczność. JEP 457 dążył do dostarczenia standardowego JDK do odczytywania, zapisywania i modyfikowania plików klasy Java, co umożliwić ma pozbycie się wewnętrznego użycia ASM w JDK. Inicjatywa ta ma też na celu zastąpienie istniejących wykorzystań biblioteki ASM w JDK, co może ostatecznie prowadzić do usunięcia ASM z JDK.
Temat stał się szczególnie kluczowy w ostatnich latach. Początkowo JVM oraz format plików klas ewoluowały stosunkowo wolno, o tyle w ostatnich latach tempo tego rozwoju znacząco się zwiększyło. Przykładowo, taka Valhalla co chwile proponuje nowe (choć często krótko żyjące) propozycje nowego bajtkody i deskryptory pól. Blokowanie rozwoju JVM na konieczności aktualizacji zewnętrznych bibliotek, takich jak ASM, staje się po prostu w związku z tym nieutrzymywalne. Posiadanie dedykowanej biblioteki standardowej do modyfikacji klas w JDK, która rozwija się w parze z JVM, usprawni elastyczność całego procesu. Dodatkowo, nastąpić ma konsolidacja wszystkich customowych rozwiązań, które w tej chwili znalazły swoje miejsce w JDK.
Więcej na temat Class File API znajdziecie w filmiku:
Dodatkowo, ukazał się też JEP 456: Unnamed Variables and Patterns, który w praktycznie niezmienionej formie stabilizuje JEP 443, znany z JDK 21.
Bardzo ciekawy jest też Draft Valhalli, dotyczący Null-Restricted Value Class Types (!). Ale tu nie będę się spieszył – myślę, że jak się ustabilizuje poświęcimy mu całą osobną sekcje.
Zainstaluj teraz i czytaj tylko dobre teksty!
2. Pulumi – Infrastructure-as-a-Code w Javie, Kotlinie i Scali
A teraz będzie nietypowo bo o… Pulumi, ale nie martwcie się, mam nienajgorszy powody. Nie wiecie czym jest Pulumi? Zdając sobie sprawę, że nie czytacie teraz „Infrastructure as a Code Weekly”, zaczniemy więc od krótkiego wyjaśnienia. A wydaje mi się, że najłatwiej to zrobić w kontrze do jego największego konkurenta – Terraforma.
Dla tych, którzy nie mają doświadczenia z DevOpsowaniem, infrastrukturą i resztą tego całego chmurowego tałatajstwa (a moja mała rada – jeśli nie macie bardzo precyzyjnie wyznaczonej ścieżki kariery, to powinniście mieć) Terraform to narzędzie do zarządzania infrastrukturą jak każdym innym kodem. No może nie całkiem każdym, bo Terraform używa specjalistycznego języka HCL do deklaratywnego opisu zasobów w chmurze. Koncepcja jest taka, że użytkownicy definiują, jak powinna wyglądać ich infrastruktura, a Terraform dba o to, by osiągnąć ten stan za pomocą providerów dla różnych platform chmurowych – wszystko w pełni deklaratywnie.
Jednak my jesteśmy tu programistami (prawda?) dlatego pora przyglądnąć się konkurencji. Pulumi, czyli główny bohater tej sekcji podobnie jak Terraform, służy do zarządzania infrastrukturą jako kodem, ale z główną różnicą: pozwala na używanie standardowych języków programowania (np. Python, TypeScript) zamiast dedykowanego języka. Dzięki temu użytkownicy Pulumi mogą wykorzystywać pętle, warunki i inne funkcje językowe podczas definiowania infrastruktury, co może wprowadzać dodatkową elastyczność w stosunku do Terraforma. Staje się to szalenie przydatne, gdy staramy się w naszej infrastrukturze robić nieco „sprytniejsze” rzeczy, niż tylko sklejanie paru yamli z internetu.
W zeszłym tygodniu Pulumi otrzymało dużą rundę finansowania na rozwój swoich narzędzi, i to w czasach, gdy Venture Capital nie są już tak łaskawe, co pokazuje potencjał narzędzia. Zbiegło się to też z problemami licencyjnymi Terraforma, o których więcej przeczytacie tutaj. Newsy te były dla mnie impulsem do podzielenia się informacjami o JVM-owych wariantach Pulumi SDK, zwłaszcza, że zamieszani w nie byli bardzo mocno inżynierowie z mojego statku-matki, VirtusLab. Jako firma jesteśmy głęboko zakorzenieni w świecie Open-Source i toolingu JVM, odpowiedzialni między innymi za kompilator Scali. Wierząc w wizję Pulumi, współpracowaliśmy z nimi nad stworzeniem oficjalnej wersji Javowej oraz wariantów w Scali i Kotlinie.
Szczególnie to ostatnie wydanie jest mi bardzo bliskie. Osobiście bardzo wierzę, że w przyszłości spełni się wizja Kotlina Multiplatform, w której będzie się dało używać jednego języka do tworzenia wszystkich „artefaktów” w projektach – ale tak całego-całego. Że kod aplikacji można pisać w Kotlinie to nikogo nie zaskoczę, ale JetBrains mocno inwestuje przecież też w Compose – czyli warstwę UI, a przecież i Gradle (o którym jeszcze dzisiaj będzie) też mocno inwestuje w Kotlin DSL. Została infra, a Terraform ze swoim HCL mocno w tym w moich oczach bruździ. Dlatego właśnie zadbaliśmy o to, żeby powstało Pulumi dla Kotlina. A konkretnie pracowała min. Julia Plewa, która to podzieliła się wizją projektu oraz „pierwszymi krokami” w artykule Pulumi Kotlin – The missing piece in Kotlin multi-platform, który bardzo celnie oddaje wizje całego projektu.
Podobną filozofie ma zresztą właśnie Besom, czyli Scala SDK dla Pulumi. Celem twórców jest, aby pozwolić programistom realnie tworzyć „pełnostackowe” aplikacje w ulubionym języku, bez babrania się w yamlach. Projekt znajdzieciecie oczywiście na Githubie. Zresztą co ja się będę rozpisywał – tutaj macie link do tweeta zapowiadającego projekt, który dobrze rozjaśnia stojącą za nim flozofię.
Kupujecie takie podejście? Jeśli tak, spróbujcie Pulumi – pisanie „infry” mi przynajmniej dawno nie dawało takiej frajdy.
3. Release Radar
JVector
Bazy Wektorowe stanowią dynamicznie rozwijającą się dziedzinę w świecie baz danych oraz w szeroko rozumianym środowisku IT, co jest związane z ich wykorzystaniem w LLM-ach. Wyszukiwanie wektorowe odgrywa bowiem kluczową rolę w nowoczesnych aplikacjach bazujących na generatywnym AI, ułatwiając programistom rozbudowanie bazy wiedzy modeli o dodatkowe dane. Dzięki temu zaawansowane modele językowe mogą dostarczać precyzyjne odpowiedzi, unikając błędów czy „halucynacji”.
Nie jest więc zaskoczeniem, że podobne technologie są opracowywane w Javie. Przypuszczam, że wielu czytelników doskonale wie, że spora część nowoczesnych rozwiązań bazodanowych opiera się na niezawodnym JVM. Należy do nich między innymi Apache Cassandra, HBase, będąca kolumnowym magazynem dla systemu plików Hadoop, czy Elasticsearch, wyszukiwarka i silnik analizy bazująca na Lucene.
JVector to napisany w czystej Javie embeddowalny silnik do wyszukiwania wektorowego, napędzający DataStax Astra oraz integrujący się z wcześniej wspomnianą Apache Cassandra. Najbliższym krewnym JVectora jest wyszukiwanie wektorowe Apache Lucene. Lucene co prawda implementuje algorytm wyszukiwania wektorowego HNSW, który jest szybki, ale zachłanny jeśli chodzi o pamięć. JVector, bazując na bardziej zaawansowanym algorytmie DiskANN, jest ponad 10 razy szybszy niż Lucene dla dużych zbiorów danych. JVector jest szybki, wydajny pod względem pamięci, świadomy dysku, równoległy, łatwy do osadzenia i przyrostowy. Projekt JVector jest przeznaczony do prostej integracji, jednocześnie zachowując wysoką wydajność – przykładwo, wykorzystuje Vector API i instrukcje SIMD z Panamy (dla przypomnienia, pozostające w inkubacji).
Gradle 8.4
Zespół Gradle przedstawił wersje 8.4. Prawdopodobnie największą nowością jest wsparcie kompilacji dla JDK 21, a wbudowany Kotlin został zaktualizowany do wersji 1.9.10. Co ciekawe, fakt że sam Kotlin 1.9.10 nie wspiera jeszcze JDK 21 sprawia, że sam Gradle wymaga do pracy JDK 20.
System Windows otrzymał w tym wydaniu również przyspieszenie kompilacji Javy oparte o compiler daemons, które pozostałe systemy dostały w wersji 8.3. Aktualizacja oferuje również pomniejsze ulepszenia, takie jak zoptymalizowanie ustawień pamięci dla narzędzi do kontroli jakości kodu (jak Checkstyle, CodeNarc, czy PMD) w większych codebase, ulepszony format raportu HTML Checkstyle oraz wsparcie JVM dystrybuowanego przez JetBrains.
Znaczącą zmianą jest też prostsze tworzenie konfiguracji skoncentrowanych na określonych rolach, z planowanym wsparciem podstawowych wtyczek Gradle do korzystania z nich już w nadchodzącej wersji 9.0. Kotlin DSL również został wzbogacony, z stabilnym wsparciem dla prostego przypisania właściwości za pomocą operatora =
.
Zainstaluj teraz i czytaj tylko dobre teksty!
Prywata i małe zaproszenie
A na koniec, jeśli mieszkacie w Pradzę albo okolicach Stuttgartu chciałem Was zaprosić na zobaczenie się face-to-face. Mój następny tydzień jest bowiem trochę szalony (co może spowodować, że nie będzie za tydzień edycji, ale będę walczył!).
Najpierw udaję się do Ludwigsburga 🇩🇪 na EclipseCon, gdzie prezentuję GraalVM, CRaC, Leyden and friends – in search of TRULY cloud-native Java 19 października.
Następnie szybka teleportacja (bo nie wiem jak to inaczej nazwać) do Pragi 🇨🇿, aby być na GeeCON 20 października.
This is how we roll 😎
Nawet jeśli nie idziecie na same konferencje, to jak ktoś chce się spotkać i napić kawy, to myślę, że uda się zorganizować. Jakby co to macie mój e-mail