JavaRush /Blog Java /Random-PL /Przerwa kawowa #37. Nowa przyszłość dla Java. Perspektywy...

Przerwa kawowa #37. Nowa przyszłość dla Java. Perspektywy JVM, Kotlin i Java po 2020 roku

Opublikowano w grupie Random-PL
Źródło: Medium Java jest krytykowana głównie za dwie rzeczy: gadatliwość i ilość szablonowego kodu, który jest generowany w wielu przypadkach bez oczywistej potrzeby. Chociaż zawsze lubiłem Javę, nie mogę powiedzieć, że te stwierdzenia są błędne. To naprawdę prawda: nadmierna szczegółowość w Javie może czasami być bardzo irytująca. Trzeba jednak przyznać, że nie żyjemy w idealnym świecie i w większości przypadków musimy wybrać mniejsze zło. Przerwa kawowa #37.  Nowa przyszłość dla Java.  Perspektywy JVM, Kotlin i Java po 2020 roku - 1Od samego początku Java nie była doskonała: wszyscy o tym wiemy, ale prawdziwym pytaniem jest, dlaczego wcześniej nie zrobiono nic, aby rozwiązać te problemy. Myślę, że jedynym powodem, dla którego zmiany trwały tak długo, był brak konkurencji w języku Java i wszystko było tak, jak było. Java zdominowała rynek, prawdopodobnie ze względu na brak poważnych konkurentów i ogromne wysiłki poczynione najpierw przez firmę Sun, a następnie przez firmę Oracle. Wysoki poziom bezpieczeństwa typów, jaki zapewnia Java, oraz dobra struktura języka sprawiły, że jest on bardzo popularny w dużych projektach. Ponadto jest to język wieloplatformowy, który działa na własnej maszynie wirtualnej. W połączeniu z automatyczną optymalizacją wydajności poprzez kompilator JIT, wszystko to minimalizuje wpływ źle napisanego kodu. Podsumowując, jest to całkiem przekonujący zestaw powodów, dla których warto używać Java. Ale co stało się później? Stało się tak, że na rynku pojawiły się nowe języki, które mogą działać w tej samej maszynie JVM co Java. Języki, które wyeliminowały niektóre z największych niedogodności w Javie i w niektórych przypadkach zaoferowały programistom przyjemniejsze środowisko z niższą barierą wejścia. Zanim przejdziemy dalej, podsumujmy i przyjrzyjmy się krótko historii języków JVM.

Historia języków JVM

Na początek chciałbym wyjaśnić jedną rzecz: nie wspomniałem o niektórych istniejących językach JVM, ponieważ nigdy nie miały one wystarczającego wsparcia, aby można je było uznać za kandydatów do powszechnego zastosowania w naszej branży. Rozpocznijmy teraz nasz krótki przegląd historii języków JVM. Na początek będziemy mieli Javę, najstarszy i najpopularniejszy język w świecie JVM. Java została oficjalnie wydana w styczniu 1996 roku, więc język ten istnieje już od 24 lat. Nieźle, prawda? Java była pierwotnie językiem czysto imperatywnym, zgodnym ze stylem programowania obiektowego; był to również język silnie typowany. Składnia języka Java jest nieco podobna do języków C++ i C, ale uważa się ją za wersję ulepszoną, ponieważ pisanie kodu w Javie jest znacznie łatwiejsze niż w C lub C++. Z drugiej strony mamy największy argument wśród jego przeciwników – gadatliwość. Drugim językiem JVM był Groovy. Istnieje od 2003 roku, chociaż jego pierwsza ujednolicona wersja, 1.0, pojawiła się dopiero w styczniu 2007 roku. Zaletą Groovy jest to, że można go używać jako języka skryptowego. Groovy jest językiem dynamicznie pisanym, więc sprawdzanie typu odbywa się w czasie wykonywania. Jest to jeden z powodów, dla których niektórzy programiści nie lubią Groovy. Piszesz swój kod w Groovy i wygląda on poprawnie w czasie kompilacji, ale w czasie wykonywania zdajesz sobie sprawę, że coś jest nie tak. Potem pojawił się kolejny popularny język: mówimy o Scali. Został wydany w 2004 roku. Wprowadził do świata JVM nowy model pracy: programowanie funkcjonalne i podejście deklaratywne. Zasadniczo Scala była pierwszym językiem, który wprowadził koncepcję niezmienności, która została następnie wykorzystana do ulepszenia języka Java. Z drugiej strony krytycy nie lubią Scali ze względu na jej skomplikowaną gramatykę i raczej niską czytelność. Następnym językiem, który wyłonił się ze świata JVM, był Clojure, język czysto funkcjonalny. Ostatnio stał się dość popularny, choć pojawił się w 2007 roku. Clojure to język oparty na LISP-ie, który charakteryzuje się prostotą i wykorzystaniem prostych funkcji. Do jego wad należy to, że jest pisany dynamicznie (jak Groovy), a krzywa uczenia się jest znacznie bardziej stroma, ponieważ jego składnia jest całkowicie odmienna od innych języków JVM. I wreszcie mamy Kotlina. Kotlin pojawił się po raz pierwszy w lutym 2016 roku i od tego czasu jego popularność nie przestaje rosnąć. Jest rozwijany przez JetBrains, a jego głównym celem jest naprawienie najsłynniejszych problemów z Javą. Z założenia Kotlin zachował wszystkie zalety Javy, ale jednocześnie rozwiązał wiele problemów. Są to najważniejsze języki JVM. Jak powiedziałem, brakowało nam kilku innych języków, które nie są tak popularne: Jython, JRuby, Ceylon, Fantom itp. Jeśli chcesz, możesz znaleźć całą listę istniejących języków JVMw Wikipedii. Prawdopodobnie zdałeś sobie sprawę, że Java nie miała zbyt dużej konkurencji przez pierwsze osiem czy dziesięć lat po jej stworzeniu, ale od tego czasu wszystko się zmieniło. Czy zatem konkurencja jest dobra czy zła?

Korzyści ze wzrostu konkurencji

Java nie zmieniła się zbytnio w swoich wczesnych latach. Pewnie dlatego, że nie było to konieczne. Język ten był powszechnie używany i zawsze cieszył się dużą popularnością, mimo że daleki był od doskonałości. Ale potem pojawili się konkurenci, bardziej nowoczesne języki, które oferowały nowe funkcje i rozwiązywały niektóre problemy, które od dawna nękały programistów Java. Przyjrzyjmy się na przykład językowi Scala. Popularność Scali rośnie od 2009 roku. Programiści z zadowoleniem przyjęli ten nowy styl funkcjonalny, który zapewnił im większą elastyczność, a także możliwość bezpiecznego i łatwego pisania kodu równoległego. Jak Oracle zareagowała na ten nowy trend? W 2014 roku pojawiły się Java Lambda i Streams. Myślę, że wszyscy możemy się zgodzić, że właśnie wtedy Java zrobiła największy krok w stronę pokonania Scali. Teraz każdy programista wie, że Scala nie jest już modna. Przerwa kawowa #37.  Nowa przyszłość dla Java.  Perspektywy JVM, Kotlin i Java po 2020 roku - 2Kolejną korzyścią wynikającą z posiadania większej liczby konkurentów w świecie JVM są ciągłe udoskonalenia kompilatora JIT i JVM. Teraz znacznie więcej osób jest zainteresowanych optymalizacją JVM i poprawą wydajności. Zatem konkurencja jest dobra dla wszystkich! Najnowszą alternatywą dla Javy jest język Kotlin. Jego pojawienie się było bardzo ważne dla rozwoju Javy, ponieważ nowy język w pewnym sensie wskazał Oracle drogę naprzód. Przykład Kotlina pokazał, że można zachować zalety Javy, ale stworzyć bardziej zwarty język, w którym szybciej pisze się kod. Jeśli spojrzysz na wykres Trendów Google, zobaczysz, że od 2016 do 2018 roku popularność Kotlina szybko rosła. Jednak w ciągu ostatnich dwóch lat emocje opadły. Przerwa kawowa #37.  Nowa przyszłość dla Java.  Perspektywy JVM, Kotlin i Java po 2020 roku - 3Oracle dokładnie przyjrzało się reakcji branży na Kotlin. Jeśli spojrzysz na informacje o wydaniu JDK 15 , zobaczysz, że niektóre nowe funkcje Java są kopiami tego, co pojawiło się w Kotlinie. Są to nowe wpisy w Javie , nowe bloki tekstowe (ciągi wielowierszowe z potrójnymi cudzysłowami) i nowy operator switch, który jest w istocie kopią operatora whenz Kotlina. Wszystko, o czym rozmawialiśmy, nazywam „kotlinizacją Javy”. Stając się silniejszym konkurentem, Kotlin pokazał Javie ścieżkę, którą należy podążać.

„Kotlinizacja” Javy

Niektóre z nadchodzących funkcji Java wprowadzą znaczną poprawę pod względem czytelności i rozwiążą jedną z największych słabości języka Java – jego gadatliwość. Można argumentować, że wiele z zapowiedzianych funkcji Java jest podejrzanie podobnych do niektórych funkcji Kotlina. Należy jednak pamiętać, że większość z nich to wersje przedpremierowe . Oznacza to, że jeśli zainstalujesz JDK 14 lub JDK 15 (kiedy zostaną wydane), domyślnie nie będziesz mógł z nich korzystać. Podglądy funkcji Java to nowe funkcje wprowadzone w danej wersji, ale domyślnie wyłączone. Zostały one uwzględnione w nowej wersji wyłącznie w celu zebrania opinii społeczności programistów, dlatego mogą jeszcze podlegać zmianom. Dlatego nie zaleca się używania ich w kodzie produkcyjnym. Aby włączyć je w czasie kompilacji, musisz wykonać następujące czynności:
javac --enable-preview --release 14
Jeśli chcesz je włączyć w czasie wykonywania, musisz uruchomić następujące czynności:
java --enable-preview YourClass
Oczywiście możesz je także włączyć w swoim IDE, ale uważaj, aby nie włączyć domyślnie podglądu we wszystkich nowych projektach! Przyjrzyjmy się zmianom, które będą miały większy wpływ na nasze kodowanie w przyszłych wersjach Javy.

Posty w Javie

Java Records to funkcja, o którą wielu z nas zabiegało od dawna. Domyślam się, że znalazłeś się w sytuacji, w której musiałeś zaimplementować toString , hashCode , równa się , a także moduły pobierające dla każdego istniejącego pola. Kotlin ma klasy danych , które rozwiązują ten problem , a Java zamierza zrobić to samo, udostępniając klasy rekordów , które Scala już posiada, w formie klas przypadków . Głównym celem tych klas jest przechowywanie niezmiennych danych w obiekcie. Weźmy przykład, aby zobaczyć, o ile lepsza może stać się Java. Oto, ile kodu musielibyśmy napisać, aby utworzyć i porównać naszą klasę Employee:
package com.theboreddev.java14;

import java.util.Objects;

public class Employee {
    private final String firstName;
    private final String surname;
    private final int age;
    private final Address address;
    private final double salary;

    public Employee(String firstName, String surname, int age, Address address, double salary) {
        this.firstName = firstName;
        this.surname = surname;
        this.age = age;
        this.address = address;
        this.salary = salary;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getSurname() {
        return surname;
    }

    public int getAge() {
        return age;
    }

    public Address getAddress() {
        return address;
    }

    public double getSalary() {
        return salary;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Employee employee = (Employee) o;
        return age == employee.age &&
                Double.compare(employee.salary, salary) == 0 &&
                Objects.equals(firstName, employee.firstName) &&
                Objects.equals(surname, employee.surname) &&
                Objects.equals(address, employee.address);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstName, surname, age, address, salary);
    }

    @Override
    public String toString() {
        return "Employee{" +
                "firstName='" + firstName + '\'' +
                ", surname='" + surname + '\'' +
                ", age=" + age +
                ", address=" + address +
                ", salary=" + salary +
                '}';
    }
}
A także obiekt, Addressktóry zawiera:
import java.util.Objects;

public class Address {
    private final String firstLine;
    private final String secondLine;
    private final String postCode;

    public Address(String firstLine, String secondLine, String postCode) {
        this.firstLine = firstLine;
        this.secondLine = secondLine;
        this.postCode = postCode;
    }

    public String getFirstLine() {
        return firstLine;
    }

    public String getSecondLine() {
        return secondLine;
    }

    public String getPostCode() {
        return postCode;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Address address = (Address) o;
        return Objects.equals(firstLine, address.firstLine) &&
                Objects.equals(secondLine, address.secondLine) &&
                Objects.equals(postCode, address.postCode);
    }

    @Override
    public int hashCode() {
        return Objects.hash(firstLine, secondLine, postCode);
    }

    @Override
    public String toString() {
        return "Address{" +
                "firstLine='" + firstLine + '\'' +
                ", secondLine='" + secondLine + '\'' +
                ", postCode='" + postCode + '\'' +
                '}';
    }
}
Prawdopodobnie jest za dużo kodu na coś tak prostego, prawda? Zobaczmy teraz, jak to będzie wyglądać z nowymi wpisami Java:
public record EmployeeRecord(String firstName, String surname, int age, AddressRecord address, double salary) {
}
I nie zapominajmy o klasie Adres:
public record AddressRecord(String firstLine, String secondLine, String postCode) {
}
To jest to samo, co pisaliśmy wcześniej z tak dużą ilością kodu. Zgadzam się: to jest niesamowite. I ilość kodu, który zaoszczędzimy, i łatwość pisania! Zobaczmy teraz, jakie są różnice w przypadku nowego operatora switch.

Ulepszony operatorswitch

Nowy operator switchw Javie rozwiąże część starych problemów, w tym niektóre błędy i duplikację kodu. Dzięki nowemu operatorowi switchten problem zostanie rozwiązany. Aby wyjaśnić to na przykładzie, utworzymy wyliczenie DayOfTheWeekw Javie:
public enum DayOfTheWeek {
    MONDAY,
    TUESDAY,
    WEDNESDAY,
    THURSDAY,
    FRIDAY,
    SATURDAY,
    SUNDAY
}
Następnie nasz switchpowie nam, która pozycja w tygodniu odpowiada temu dniu. Zobaczmy najpierw, jak możemy to zrobić obecnie, używając Java 11.
final DayOfTheWeek dayOfTheWeek = DayOfTheWeek.THURSDAY;

        int position = 0;

        switch (dayOfTheWeek) {
            case MONDAY:
                position = 1;
                break;
            case TUESDAY:
                position = 2;
                break;
            case WEDNESDAY:
                position = 3;
                break;
            case THURSDAY:
                position = 4;
                break;
            case FRIDAY:
                position = 5;
                break;
            case SATURDAY:
                position = 6;
                break;
            case SUNDAY:
                position = 7;
                break;
        }

        System.out.println("Day " + dayOfTheWeek + " is in position " + position + " of the week");
W przypadku bieżącej instrukcji switchbędziemy musieli użyć zmiennej, a jeśli pominiemy któryś dzień tygodnia, nasz kod skompiluje się poprawnie. Jest to jeden z problemów operatorów switch: są zbyt podatni na błędy. Jak więc Java 14 poprawia sytuację? Przyjrzyjmy się:
final DayOfTheWeek dayOfTheWeek = DayOfTheWeek.THURSDAY;

        int position = switch (dayOfTheWeek) {
            case MONDAY -> 1;
            case TUESDAY -> 2;
            case WEDNESDAY -> 3;
            case THURSDAY -> 4;
            case FRIDAY -> 5;
            case SATURDAY -> 6;
            case SUNDAY -> 7;
        };

        System.out.println("Day " + dayOfTheWeek + " is in position " + position + " of the week");
Jak widać, nowych operatorów switchmożna używać jako wyrażenia, a nie tylko jako instrukcji. Rezultat jest bardziej zwięzły i wyrazisty. To wystarczyłoby, aby przekonać wielu z nas do ich użycia, ale jednym z głównych ulepszeń jest to, że teraz instrukcje switchnie będą się kompilować, chyba że uwzględnimy wszystkie przypadki w naszym pliku switch. Pokaże nam ten błąd, na przykład:
Error:(9, 24) java: the switch expression does not cover all possible input values
Od teraz pominięcie wielkości liter u naszych operatorów nie będzie możliwe switch. Jest to bardzo podobne do operatorów whenw Kotlinie, o których możesz przeczytać w dokumentacji . Przyjrzyjmy się także nowym blokom tekstowym.

Bloki tekstowe

Czy kiedykolwiek narzekałeś, jak trudno jest przypisać obiekt blob JSON do zmiennej w Javie? Java zawiera sekwencje wielowierszowe, które można opisać, umieszczając je w potrójnych cudzysłowach. Po oficjalnym udostępnieniu tej funkcji opisywanie długich sekwencji składających się z wielu linii stanie się znacznie łatwiejsze. Przyjrzyjmy się różnicom pomiędzy tymi dwoma trybami. Jeśli chcemy użyć sformatowanego JSON w zmiennej, okazuje się, że jest źle:
final String text = "{\"widget\": {\n" +
                "    \"debug\": \"on\",\n" +
                "    \"window\": {\n" +
                "        \"title\": \"Sample Konfabulator Widget\",\n" +
                "        \"name\": \"main_window\",\n" +
                "        \"width\": 500,\n" +
                "        \"height\": 500\n" +
                "    },\n" +
                "    \"image\": { \n" +
                "        \"src\": \"Images/Sun.png\",\n" +
                "        \"name\": \"sun1\",\n" +
                "        \"hOffset\": 250,\n" +
                "        \"vOffset\": 250,\n" +
                "        \"alignment\": \"center\"\n" +
                "    },\n" +
                "    \"text\": {\n" +
                "        \"data\": \"Click Here\",\n" +
                "        \"size\": 36,\n" +
                "        \"style\": \"bold\",\n" +
                "        \"name\": \"text1\",\n" +
                "        \"hOffset\": 250,\n" +
                "        \"vOffset\": 100,\n" +
                "        \"alignment\": \"center\",\n" +
                "        \"onMouseUp\": \"sun1.opacity = (sun1.opacity / 100) * 90;\"\n" +
                "    }\n" +
                "}} ";
Z drugiej strony, kiedy zostaną wydane nowe bloki tekstowe, wszystko stanie się znacznie prostsze:
final String multiLineText = """
                {"widget": {
                    "debug": "on",
                    "window": {
                        "title": "Sample Konfabulator Widget",
                        "name": "main_window",
                        "width": 500,
                        "height": 500
                    },
                    "image": {\s
                        "src": "Images/Sun.png",
                        "name": "sun1",
                        "hOffset": 250,
                        "vOffset": 250,
                        "alignment": "center"
                    },
                    "text": {
                        "data": "Click Here",
                        "size": 36,
                        "style": "bold",
                        "name": "text1",
                        "hOffset": 250,
                        "vOffset": 100,
                        "alignment": "center",
                        "onMouseUp": "sun1.opacity = (sun1.opacity / 100) * 90;"
                    }
                }}
                """;
To zdecydowanie wygląda lepiej. Wszystko to jest już obsługiwane w Kotlinie, jak widać w definicjach typów . Widzieliśmy więc, że Java „dziedziczy” wiele rozwiązań swoich problemów od jednego ze swoich konkurentów: Kotlina. Nie wiemy, czy Oracle zareagowało na czas, aby powstrzymać rozwój Kotlina, czy też przyszło za późno. Osobiście uważam, że Java robi właściwe kroki do przodu, nawet jeśli zmiany te zostały w jakiś sposób zainicjowane przez konkurencję i mogą nastąpić z pewnym opóźnieniem.

Wniosek

Myślę, że konkurencja to najlepsza rzecz, jaka kiedykolwiek przydarzyła się językowi Java. Mam wrażenie, że w przeciwnym razie Java spoczęłaby na laurach. Dodatkowo konkurenci Javy pokazali, że możliwy jest inny sposób programowania, pokazując, jak iść do przodu i unikać przestarzałych i nieefektywnych sposobów pisania kodu. Przyszłe zmiany sprawią, że Java stanie się potężniejsza niż kiedykolwiek wcześniej, będzie to język dostosowany do epoki nowożytnej, język, który chce ewoluować.
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION