Java 10

woman head

Do tej pory na kolejne wersje Javy musieliśmy czekać ok 2-3 lata. Najdłuższy przestój był pomiędzy Javą 6 (2006) i 7 (2011) i wynosił aż 5 lat (był to czas przejęcia firmy Sun przez Oracle). Pomimo iż na wersję 8 trzeba było czekać kolejne 3 lata, to przyniosła ona ogromne zmiany w tym jak tworzymy kod. Interfejsy funkcyjne, wyrażenia lambda, strumienie sprawiły, że wizerunek Javy znacznie się poprawił, a programowanie w niej stało się po prostu dużo przyjemniejsze. Java 9 z kolei choć nie wprowadza wielu nowości z punktu widzenia pisanego kodu, to udostępnia system modułów wprowadzający nowe możliwości w tworzeniu aplikacji, czy interaktywny interpreter JShell, który jest wygodnym narzędziem choćby do testowania prostych fragmentów kodu bez konieczności tworzenia klas, czy uruchamiania IDE. Choć Java 9 jeszcze na dobre się nie zadomowiła, to już niebawem, bo w marcu 2018 roku pojawi się Java 10, a konkretnie 10.3. Nowe wersje Javy będą się teraz pojawiały dwa razy w roku - w marcu i we wrześniu. Początkowo wersjonowanie miało opierać się o rok i miesiąc, czyli zamiast Javy 10.3 mielibyśmy przeskok do 18.3, później 18.9 itd. ale ze względu na krytyczne komentarze społeczności postanowiono pozostać przy naturalnym porządku. A co nas czeka w nadchodzących wersjach? Sporo ciekawych rzeczy, przyjrzyjmy im się.

1. Rozszerzone wnioskowanie typów i var

We wczesnych wersjach Javy używanie typów generycznych wymagało powtarzania używanych typów przy deklaracji inicjalizacji pomimo tego, że w zasadzie nie ma tam w zasadzie większego sensu:

List<String> names = new ArrayList<String>();

Zauważono jednak, że da się taki zapis skrócić i w ramach projektu Coin wprowadzono diamond operator, czyli możliwość pominięcia powtórzonego typu generycznego przy wywołaniu konstruktora:

List<String> names = new ArrayList<>();

Choć niewiele osób zdaje sobie z tego sprawę, to został tutaj rozszerzony mechanizm wnioskowania typów, który dostępny był już sporo wcześniej, ale tylko na poziomie metod i dzięki temu nie musimy pisać:

List<String> names = Collections.<String>emptyList();

tylko po prostu:

List<String> names = Collections.emptyList();

(zgaduję, że nawet nigdy nie spotkałeś się z pierwszym zapisem? :)). Obecnie trwają pracę w ramach projektu Amber nad poszerzeniem wnioskowania typów i wprowadzenia wnioskowania typów dla deklarowanego typu zmiennych lokalnych. W skrócie będziemy mogli używać dzięki temu zapisów typu:

var names = new ArrayList<String>();

co jak widać działa podobnie jak diamond operator, tylko "w druga stronę". Szczególnie użyteczne będzie to w sytuacji używania długich nazw klas, przykładowo:

var mojfajnyObiekt = new MojaSuperFajnaKlasa();
//zamiast
MojaSuperFajnaKlasa mojfajnyObiekt = new MojaSuperFajnaKlasa();

Nie należy jednak mylić tej funkcjonalności z dynamicznym typowaniem znanym choćby z JavaScriptu. Nie będzie można zapisać np.:

var value = "abcd";
value = 5; //błąd, value jest Stringiem

2. Uogólniony switch

Przed Javą 7 switch w Javie działał wyłącznie na wartościach całkowitoliczbowych i wartościach wyliczeniowych (enum). Od Javy 7 rozszerzono switcha o możliwość używania Stringów (choć co ciekawe i w tym przypadku pod spodem wykorzystywany jest switch na liczbach). Tym razem zmiana miałaby być dużo większa i zasadniczo w switchu moglibyśmy używać niemal dowolnych wartości. Przykład z opisu JEP 305:

String formatted;
switch (obj) {
    case Integer i: formatted = String.format("int %d", i); break;
    case Byte b:    formatted = String.format("byte %d", b); break;
    case Long l:    formatted = String.format("long %d", l); break;
    case Double d:  formatted = String.format("double %f", d); break;
    case String s:  formatted = String.format("String %s", s); break
    default:        formatted = obj.toString();
}

Jak widać do switcha będzie można zasadniczo przekazać niemal cokolwiek, a następnie w blokach case nastąpi odpowiednie dopasowanie do wzorca. Docelowo ma tutaj także działać coś co Brian Goetz nazywa dekonstrukcją obiektów. Przykładowo mając klasę Punkt:

datum Punkt(
  int x;
  int y;
}

obiekt tego typu będzie można przekazać do switcha:

Punkt punkt1 = new Punkt(5, 10);

switch(punkt1) {
  case Punkt(var a, var b) -> System.out.println(a + " " + b);
  //...
}

gdzie w bloku case nastąpi dekonstrukcja i do zmiennych a i b zostaną przypisane wartości pól x i y z wcześniej utworzonego obiektu (czyli odpowiednio 5 i 10).

3. Data classes i sealed interfaces

Halo halo, co to za słowo datum w poprzednim przykładzie?! Przecież to nie jest żadna klasa! No właśnie wygląda na to, że niebawem będzie to słowo używane do definiowania tzw. Data Classes. Chyba żaden programista Javy nie jest zbyt szczęśliwy, gdy musi tworzyć klasy typu:

public class Point {
    private int x;
    private int y;
    
    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }
    public int getX() {
        return x;
    }
    public void setX(int x) {
        this.x = x;
    }
    public int getY() {
        return y;
    }
    public void setY(int y) {
        this.y = y;
    }
}

gdzie zasadniczo jedyne istotne elementy to nazwa klasy i definicja pól, natomiast rzeczy takie jak gettery, settery, konstruktory generuje za nas IDE. Niektórzy radzą sobie z tym problemem używając projektu Lombok jednak bywa to kłopotliwe i jest to de facto po prostu magiczny generator kodu. Data classes mają rozwiązywać ten problem na poziomie samego języka i będą mówiły programiście "jestem kontenerem danych, kropka". Powyższa klasa będzie więc mogła być prawdopodobnie zastąpiona zapisem:

datum Punkt(
  int x;
  int y;
}

Oprócz data classes pojawią się także sealed interfaces (wybaczcie, że nie tłumaczę "klas danych", czy "szczelnych/zapieczętowanych interfejsów"). W skrócie będą to interfejsy używane do definiowania ścisłych hierarchii typów. Przykład przytoczony przez Briana Goetza:

sealed interface Shape { }

datum Point (int x, int y);

datum Circle(Point center, int radius) implements Shape;
datum Rectangle(Point lowerLeft, Point upperRight) implements Shape;

Co nam da użycie sealed interface? Kompilator będzie w stanie na tej podstawie domyślić się pewnych rzeczy. Przykład jest dosyć abstrakcyjny, ale jeśli coś takiego by działało, to wow:

Shape shape = ... //jakiś Circle albo Rectangle
double area = switch(shape) {
  case Circle(var center, var r) -> Math.PI * r * r;
  case Rectangle(var ll, var ur) -> (ur.x - ll.x) * (ur.y - ll.y);
}

Dzięki użyciu sealed interface kompilator jest w stanie zauważyć, że "oho jedyne implementacje tego interfejsu to Circle i Rectangle, więc w switchu nie jest potrzebny blok default". Drugą kwestią jest to, że w przykładzie pokazanym przez Briana switch mógłby być używany w sposób podobny jak metody, czyli mógłby zwracać pewną wartość, którą można by przypisać do zmiennej. Na chwilę obecną wygląda to jakby to w ogóle nie była Java, ale na takie rzeczy warto będzie chwilę poczekać. Co ciekawe część tych funkcjonalność można uzyskać nawet dziś używając kotlina, czyli alternatywnego języka dla wirtualnej maszynie Javy. Na pewno nie wszystkie z wyżej wspomnianych funkcjonalności pojawią się w Javie 10.3. Kiedy w takim razie się pojawią? Na razie można odpowiedzieć jedynie "jak będą to będą", ale przyszłość rysuje się interesująco. Jeśli macie godzinkę czasu to polecam obejrzeć prezentację Briana z konferencji Devoxx Belgium 2017

Dyskusja i komentarze

Masz pytania do tego wpisu? Może chcesz się podzielić spostrzeżeniami? Zapraszamy dyskusji na naszej grupie na Facebooku.