Java jeszcze nie umarła, ale Valhalli nie uniknie

softwaregarden.dev 1 tydzień temu

Niedawno zobaczyłem gdzieś w mediach społecznościowych fragment kodu. Jak mniemam, miał pokazać, iż “Java jest zła i martwa”. Myślę, iż to dobry moment, żeby przyjrzeć się niektórym twierdzeniom, nieprawdom i półprawdom, które pojawiły się w komentarzach i nie powiem, choćby mnie rozbawiły. Przy okazji odpowiem na część zarzutów, które padły podczas kilku wykładów konferencyjnych jesienią (sprawdza kalendarz) 2026 roku!

TL;DR: kompaktowe pliki źródłowe już są z nami, obiekty value z Valhalli nadchodzą, możesz przez cały czas tkwić mentalnie w 2005 roku, ale miej świadomość, który rok mamy teraz.

Zagadka

Fragment kodu (odtwarzam go z pamięci) wyglądał mniej więcej tak:

void main() { Integer a = 127, b = 127, c = 128, d = 128; IO.println(a == b); IO.println(c == d); }

Oczywiście, żeby udowodnić tezę, wykorzystywał System.out.println(). Ponieważ nie żyję już na początku lat dwutysięcznych, pozwoliłem sobie unowocześnić go dzięki IO.println.

To może być jedna z tych zagadek, którymi opchani własnym ego po grdykę Super Senior Software Engineerowie dręczą kandydatów ;-) Pozwólcie zatem, iż zadam dwa pytania:

Pytania

  • Której wersji Javy potrzebujemy, żeby uruchomić ten kod?
  • Co wypisze ten kod?

Wersje

Odpowiedź na pierwsze pytanie to (jeśli liczymy tylko funkcje GA) 25. Została wydana ponad rok temu! Dzięki JEP-512 (oraz dwóm poprzedzającym go JEPom w wersji preview) naprawdę nie ma potrzeby tkwić w przeszłości i wciąż powtarzać: “och, uruchomienie czegokolwiek w Javie wymaga tyle ceremoniału”.

Może nie dotarła do Ciebie notatka służbowa, a może ominęła Cię impreza sylwestrowa 2025/2026, ale teraz nie potrzebujesz już public static void main (String[] args) opakowanej w class. To znaczy, przez cały czas możesz tak pisać, ale nie jest to obowiązkowe, zwłaszcza jeżeli chcesz uczyć ludzi stawiających pierwsze kroki w Javie. A jako doświadczony Software Engineer możesz tego używać także do prototypowania!

Co więcej, dostajemy zestaw wygodnych, przeciążonych metod IO.println() i IO.readln(), więc pisanie na standardowe wyjście i czytanie ze standardowego wejścia (czytaj: klawiatury) nie jest już tak upierdliwe. Jasne, jeżeli IO. przyprawia Cię o ból głowy, możesz wykorzystać static import, ale dla nowicjuszy w inżynierii systemu czyni to sprawy mniej oczywistymi, więc może jednak nie powinniśmy. Osobiście lubię ten prefiks, ponieważ jasno pokazuje intencję i to, iż mamy do czynienia z WeWy (tak mówiliśmy na przełomie wieków na IO).

Możesz więc skopiować powyższy kod, zapisać go w pliku, powiedzmy riddle.java, a następnie uruchomić dzięki Javy 25 lub nowszej:

java riddle.java

I to naprawdę wszystko! Nie trzeba najpierw traktować go poleceniem javac! Serio, mamy koniec 2026 roku; nie ma co marudzić o doświadczeniach naszych rodziców. Świat idzie naprzód, Java idzie naprzód, może my też powinniśmy?

Wynik

Niektórym bardzo spodoba się odpowiedź na drugie pytanie, ponieważ… “to zależy”! ;-)

Zaraz, jak to zależy? Czy wynik nie powinien być zawsze taki sam?

Cóż, zwykle jest ;-) Zanim jednak na to odpowiemy, pozwólcie mi zboczyć z drogi i coś wyjaśnić.

  • Tak, w Javie nie można przeciążać operatorów (choć pozwalają na to choćby niektóre inne języki działające na JVMie).
  • Tak, równość obiektów sprawdzamy dzięki metody equals (czy nam się to podoba, czy nie, jest, jak jest).
  • Tak, operator ==,
    • w przypadku obiektów sprawdza, czy mamy do czynienia z tym samym obiektem, ponieważ sprawdza, gdzie obiekt znajduje się w pamięci,
    • w przypadku typów prymitywnych porównuje ich wartość.

Przypomnienie: typy prymitywne w Javie to na przykład int, long, double, boolean. Stringi oraz Integery (podlegające [auto]boxingowi) są obiektami, a nie typami prymitywnymi. Dlatego jeżeli mamy Integer c = 128, d = 128;, a następnie wykonamy c == d, porównujemy referencje, nie wartości przechowywane w obiektach utworzonych przez [auto]boxing.

I tutaj robi się ciekawie

Dlatego c == d daje false: utworzyliśmy dwa różne obiekty, czy nam się to podoba, czy nie. Ale Integer a = 127, b = 127;, a następnie a == b, daje nam… true. I tak, trzeba to w zasadzie wiedzieć, ponieważ Integery dla wartości od -128 do 127 są przechowywane w puli. Kiedy więc poddamy je autoboxingowi, nie otrzymamy dwóch różnych instancji; zarówno a, jak i b wskazują na dokładnie ten sam obiekt. Wynik naszej zagadki to zatem:

true false

To zaraz się zmieni

Kiedy? Najprawdopodobniej od marca 2027 roku, kiedy ma zostać wydana Java 28. A choćby wtedy nie dla wszystkich, tylko dla osób chętnych do wypróbowania funkcji preview ;-)

Według dzisiejszych przewidywań Java 28 ma zawierać JEP 401, Value Objects (preview). Będzie to pierwsza wersja Javy, w której Value Objects staną się dostępne (poza wcześniejszymi specjalnymi buildami). Klasa Integer, czyli główna bohaterka przykładu z tego wpisu, od Javy 16 jest oznaczona adnotacją @ValueBased, a w Javie 28 jest także value, jeżeli zdecydujemy się na funkcje preview.

Napiszę jeszcze więcej o obiektach value w Javie, ponieważ teraz, dla nas, to już nie jest pieśń dalekiej przyszłości. Na razie krótkie wyjaśnienie: jeżeli dodamy do klasy value, JVM nie utworzy nowego obiektu tej klasy, o ile istnieje już jej obiekt przechowujący dokładnie taką samą wartość (lub wartości).

Kiedy w naszym przykładzie wykorzystamy (wczesny build) Javy 28 z --enable-preview, Integer c = 128; utworzy nową instancję. Natomiast Integer d = 128; nie utworzy kolejnej; dostaniemy ten sam obiekt, który został utworzony dla c, a zatem c == d da nam true.

Rozumowanie, powiedziałbym, jest całkiem proste. 128 to 128. Nie zmienia się. Gdyby się zmieniało, byłoby 127 albo 129, a to zupełnie inna liczba. Nie musimy więc tworzyć opakowanego 128 za każdym razem, gdy użytkownik go potrzebuje; możemy utworzyć je raz i (dopóki nie zostanie usunięte przez garbage collector) zwracać wciąż dokładnie ten sam obiekt reprezentujący 128. choćby jeżeli znajduje się poza “zakresem puli” Integerów.

Na zagadkę można zatem odpowiedzieć także:

true true

jeśli zaznaczymy, iż uruchomiliśmy Javę 28 z włączonymi funkcjami preview, tak jak na zrzucie ekranu poniżej.

Podsumowanie

  • Gdy chcesz ponarzekać na Javę, upewnij się, proszę, iż narzekasz na którąś z ostatnich / najnowszą wersję. W przeciwnym razie to tak, jakby wyciągnąć starożytną mapę świata i upierać się, iż Ziemia jest płaska, bo tak pokazuje mapa. Fun fact: jest okrągła. (Nawet jeżeli tylko “okrągła niczym naleśnik”).
  • Ocenianie różnych rzeczy, wyrabianie sobie zdania i dzielenie się nim w uprzejmy sposób to droga do postępu w ogólności, a w szczególności do postępu Javy; dlatego uruchamianie funkcji preview, korzystanie z modułów inkubacyjnych oraz dzielenie się tym, czego się nauczyliśmy, co zrobiliśmy, co nam się podobało lub nie, jest naszą drogą nowoczesnego samuraja.
  • void main() istnieje i jest w porządku.
  • Obiekty value zaraz wstrząsną i zmienią nasze myślenie o przetwarzaniu i przechowywaniu danych w Javie.

Po więcej takich meta-narzekań naciśnij Subskrybuj i dzwoneczek :-P

Idź do oryginalnego materiału