JavaRush /Blog Java /Random-PL /Od 8 do 13: pełny przegląd wersji Java. Część 1

Od 8 do 13: pełny przegląd wersji Java. Część 1

Opublikowano w grupie Random-PL
Kociaki, cześć wszystkim)) Dziś mamy rok 2020 i niewiele pozostało do wydania Java 14. Gotowej wersji należy spodziewać się 17 marca, po fakcie przeanalizujemy, co tam jest świeżego i interesującego, ale dzisiaj chciałbym odświeżyć sobie pamięć o poprzednich wersjach Javy. Co nowego nam przynieśli? Przyjrzyjmy się. Zacznijmy recenzję od Java 8, ponieważ jest ona nadal dość aktualna i jest używana w większości projektów. Od 8 do 13: pełny przegląd wersji Java.  Część 1 - 1Wcześniej nowe wersje wypuszczano co 3-5 lat, ale ostatnio Oracle zastosowało inne podejście – „nowa Java co sześć miesięcy”. I tak co sześć miesięcy widzimy wydawanie nowych funkcji. Czy to dobrze, czy źle, każdy widzi to inaczej. Na przykład nie bardzo mi się to podoba, bo nowe wersje nie mają wielu nowości, a jednocześnie wersje rosną jak grzyby po deszczu. Kilka razy mrugnąłem na temat projektu z Javą 8, a Java 16 została już wypuszczona (ale kiedy pojawia się rzadko, gromadzą się nowe funkcje i w końcu to wydarzenie jest długo wyczekiwane, jak wakacje: wszyscy dyskutują o nowe pyszności, obok których nie można przejść obojętnie). Więc zacznijmy!

Java 8

Funkcjonalny interfejs

Co to jest? Interfejs funkcjonalny to interfejs zawierający jedną niezaimplementowaną (abstrakcyjną) metodę. @FunctionalInterface to opcjonalna adnotacja umieszczana nad takim interfejsem. Należało sprawdzić, czy spełnia wymagania interfejsu funkcjonalnego (posiadającego tylko jedną metodę abstrakcyjną). Ale jak zawsze mamy pewne zastrzeżenia: metody domyślne i statyczne nie podlegają tym wymaganiom. Zatem może być kilka takich metod + jedna abstrakcyjna, a interfejs będzie funkcjonalny. Może także zawierać metody klasy Object, które nie wpływają na definicję interfejsu jako funkcjonalnego. Dodam kilka słów o metodach domyślnych i statycznych:
  1. Metody z domyślnym modyfikatorem umożliwiają dodawanie nowych metod do interfejsów bez psucia ich istniejącej implementacji.

    public interface Something {
      default void someMethod {
          System.out.println("Some text......");
      }
    }

    Tak, tak, do interfejsu dodajemy zaimplementowaną metodę i implementując tę ​​metodę nie można jej przesłonić, lecz wykorzystać ją jako odziedziczoną. Ale jeśli klasa zaimplementuje dwa interfejsy z daną metodą, wystąpi błąd kompilacji, a jeśli zaimplementuje interfejsy i odziedziczy klasę za pomocą pewnej identycznej metody, metoda klasy nadrzędnej będzie nakładać się na metody interfejsu i wyjątek nie zadziała.

  2. metody statyczne w interfejsie działają tak samo jak metody statyczne w klasie. Pamiętaj: nie możesz dziedziczyć metod statycznych , tak samo jak nie możesz wywołać metody statycznej z klasy potomnej.

A więc jeszcze kilka słów o interfejsach funkcjonalnych i ruszajmy dalej. Oto główne listy FI (reszta to ich odmiany):

    Predykat - przyjmuje wartość T jako argument, zwraca wartość logiczną.

    Przykład:boolean someMethod(T t);

  • Konsument - pobiera argument typu T, nic nie zwraca (void).

    Przykład:void someMethod(T t);

  • Dostawca - nie pobiera niczego jako danych wejściowych, ale zwraca pewną wartość T.

    Przykład:T someMethod();

  • Funkcja - przyjmuje na wejście parametr typu T, zwraca wartość typu R.

    Przykład:R someMethod(T t);

  • UnaryOperator - pobiera argument T i zwraca wartość typu T.

    Przykład:T someMethod(T t);

Strumień

Strumienie to sposób obsługi struktur danych w funkcjonalnym stylu. Zazwyczaj są to kolekcje (ale można ich używać w innych, mniej powszechnych sytuacjach). Mówiąc bardziej zrozumiałym językiem, Stream to strumień danych, który przetwarzamy tak, jakbyśmy pracowali ze wszystkimi danymi jednocześnie, a nie metodą brute-force, jak w przypadku for-each. Spójrzmy na mały przykład. Powiedzmy, że mamy zestaw liczb, który chcemy przefiltrować (mniejszy niż 50), zwiększyć o 5 i wyprowadzić pierwsze 4 liczby z pozostałych na konsolę. Jak byśmy to zrobili wcześniej:
List<Integer> list = Arrays.asList(46, 34, 24, 93, 91, 1, 34, 94);

int count = 0;

for (int x : list) {

  if (x >= 50) continue;

  x += 5;

  count++;

  if (count > 4) break;

  System.out.print(x);

}
Wydaje się, że nie ma zbyt wiele kodu, a logika jest już trochę zagmatwana. Zobaczmy jak będzie to wyglądało przy użyciu strumienia:
Stream.of(46, 34, 24, 93, 91, 1, 34, 94)

      .filter(x -> x < 50)

      .map(x -> x + 5)

      .limit(4)

      .forEach(System.out::print);
Strumienie znacznie upraszczają życie, zmniejszając ilość kodu i czyniąc go bardziej czytelnym. Dla tych, którzy chcą zgłębić ten temat bardziej szczegółowo, oto dobry (powiedziałbym nawet doskonały) artykuł na ten temat .

lambda

Być może najważniejszą i długo oczekiwaną funkcją jest pojawienie się lambd. Co to jest lambda? Jest to blok kodu, który można przekazywać w różne miejsca, aby można go było wykonać później tyle razy, ile potrzeba. Brzmi dość zagmatwanie, prawda? Mówiąc najprościej, za pomocą lambd można zaimplementować metodę interfejsu funkcjonalnego (rodzaj implementacji klasy anonimowej):
Runnable runnable = () -> { System.out.println("I'm running !");};

new Thread(runnable).start();
Szybko i bez zbędnej biurokracji zaimplementowaliśmy metodę run(). I tak: Runnable to funkcjonalny interfejs. Lambd używam również podczas pracy ze strumieniami (jak w powyższych przykładach ze strumieniami). Nie będziemy wchodzić zbyt głęboko, ponieważ możemy nurkować dość głęboko, zostawię kilka linków, aby goście, którzy wciąż są kopaczami, mogli kopać głębiej:

dla każdego

W Javie 8 dostępna jest nowa funkcja foreach, która działa ze strumieniem danych takim jak strumień. Oto przykład:
List<Integer> someList = Arrays.asList(1, 3, 5, 7, 9);

someList.forEach(x -> System.out.println(x));
(analogicznie do SomeList.stream().foreach(…))

Odniesienie do metody

Metody referencyjne to nowa, użyteczna składnia zaprojektowana do odwoływania się do istniejących metod lub konstruktorów klas lub obiektów Java poprzez :: Odniesienia do metod występują w czterech typach:
  1. Link do projektanta:

    SomeObject obj = SomeObject::new

  2. Odniesienie do metody statycznej:

    SomeObject::someStaticMethod

  3. Odniesienie do niestatycznej metody obiektu określonego typu:

    SomeObject::someMethod

  4. Odniesienie do zwykłej (niestatycznej) metody określonego obiektu

    obj::someMethod

Często odniesienia do metod są używane w strumieniach zamiast w lambdach (metody referencyjne są szybsze niż lambdy, ale mają gorszą czytelność).
someList.stream()

        .map(String::toUpperCase)

      .forEach(System.out::println);
Dla tych, którzy chcą więcej informacji na temat metod referencyjnych:

Czas API

Dostępna jest nowa biblioteka do pracy z datami i godzinami - java.time. Od 8 do 13: pełny przegląd wersji Java.  Część 1 - 2Nowe API jest podobne do dowolnego Joda-Time. Najważniejsze sekcje tego interfejsu API to:
  • LocalDate to konkretna data, na przykład - 2010-01-09;
  • LocalTime - czas uwzględniający strefę czasową - 19:45:55 (analogicznie do LocalDate);
  • LocalDateTime — kombinacja LocalDate + LocalTime — 2020-01-04 15:37:47;
  • ZoneId - reprezentuje strefy czasowe;
  • Zegar - za pomocą tego typu można uzyskać dostęp do aktualnej godziny i daty.
Oto kilka naprawdę interesujących artykułów na ten temat:

Opcjonalny

Jest to nowa klasa w pakiecie java.util , opakowanie wartości, którego sztuczka polega na tym, że może również bezpiecznie zawierać wartość null . Odbieranie opcjonalne: Jeśli przekażemy wartość nullOptional<String> someOptional = Optional.of("Something"); w Option.of , otrzymamy nasz ulubiony wyjątek NullPointerException . W takich przypadkach stosują: - w tej metodzie nie musisz bać się wartości null. Następnie utwórz początkowo pusty Opcjonalny: Aby sprawdzić, czy jest pusty, użyj: zwróci nam wartość true lub false. Wykonaj określoną akcję, jeśli istnieje wartość i nie rób nic, jeśli nie ma wartości: Metoda odwrotna, która zwraca przekazaną wartość, jeśli Opcjonalna jest pusta (coś w rodzaju planu zapasowego): Możesz kontynuować przez bardzo, bardzo długi czas ( na szczęście Opcjonalne dodało metody obiema hojnymi rękami), ale nie będziemy się nad tym rozwodzić. Lepiej dla mnie będzie zostawić kilka linków na początek: Optional<String> someOptional = Optional.ofNullable("Something");Optional<String> someOptional = Optional.empty();someOptional.isPresent();someOptional.ifPresent(System.out::println);System.out.println(someOptional.orElse("Some default content")); Omówiliśmy najsłynniejsze innowacje w Javie 8 – to nie wszystko. Jeżeli chcesz dowiedzieć się więcej to zostawiłem Ci to:

Java 9

Tak więc 21 września 2017 r. świat ujrzał JDK 9. Ta Java 9 zawiera bogaty zestaw funkcji. Chociaż nie ma nowych koncepcji językowych, nowe interfejsy API i polecenia diagnostyczne z pewnością zainteresują programistów. Od 8 do 13: pełny przegląd wersji Java.  Część 1 - 4

JShell (REPL - pętla odczytu-ewaluacji-drukowania)

Jest to implementacja interaktywnej konsoli w języku Java, która służy do testowania funkcjonalności i używania różnych konstrukcji konsoli, takich jak interfejsy, klasy, wyliczenia, operatory itp. Aby uruchomić JShell, wystarczy napisać jshell w terminalu. Następnie możemy napisać wszystko, na co pozwala nasza wyobraźnia: Od 8 do 13: pełny przegląd wersji Java.  Część 1 - 5Używając JShell, możesz tworzyć metody najwyższego poziomu i używać ich w ramach tej samej sesji. Metody będą działać podobnie jak metody statyczne, z tą różnicą, że słowo kluczowe static można pominąć.Więcej informacji znajdziesz w podręczniku Java 9 REPL (JShell) .

Prywatny

Począwszy od wersji 9 Javy mamy możliwość stosowania w interfejsach metod prywatnych (metod domyślnych i statycznych, gdyż po prostu nie możemy przesłonić innych ze względu na niewystarczający dostęp). private static void someMethod(){} try-with-resources Ulepszono możliwość obsługi wyjątków Try-With-Resources:
BufferedReader reader = new BufferedReader(new FileReader("....."));
  try (reader2) {
  ....
}

Modułowość ( układanka )

Moduł to grupa powiązanych pakietów i zasobów wraz z nowym plikiem deskryptora modułu. To podejście służy do rozluźnienia sprzężenia kodu. Luźne powiązanie jest kluczowym czynnikiem zapewniającym łatwość konserwacji i rozszerzalności kodu. Modułowość jest realizowana na różnych poziomach:
  1. Język programowania.
  2. Maszyna wirtualna.
  3. Standardowe API Javy.
JDK 9 zawiera 92 moduły: możemy z nich korzystać lub stworzyć własne. Oto kilka linków umożliwiających głębsze spojrzenie:

Niezmienna kolekcja

W Javie 9 stało się możliwe tworzenie i wypełnianie kolekcji jedną linią, jednocześnie czyniąc ją niezmienną (poprzednio, aby stworzyć niezmienną kolekcję, musieliśmy stworzyć kolekcję, wypełnić ją danymi i wywołać metodę, na przykład Kolekcje.niemodyfikowalna lista). Przykład takiej kreacji: List someList = List.of("first","second","third");

Inne innowacje:

  • rozszerzony Opcjonalny (dodano nowe metody);
  • interfejsy ProcessHandle i ProcessHandle wydawały się kontrolować działania systemu operacyjnego;
  • G1 - domyślny moduł zbierający śmieci;
  • Klient HTTP obsługujący zarówno protokoły HTTP/2, jak i WebSocket;
  • rozszerzony strumień;
  • dodano framework Reactive Streams API (do programowania reaktywnego);
Aby uzyskać pełniejsze zanurzenie się w Javie 9, radzę przeczytać:

Java 10

Tak więc sześć miesięcy po wydaniu Java 9, w marcu 2018 roku (pamiętam to jak wczoraj), na scenę wkroczyła Java 10. Od 8 do 13: pełny przegląd wersji Java.  Część 1 - 6

odm

Teraz nie musimy podawać typu danych. Oznaczamy wiadomość jako var, a kompilator określa typ wiadomości na podstawie typu inicjatora znajdującego się po prawej stronie. Ta funkcja jest dostępna tylko dla zmiennych lokalnych z inicjatorem: nie można jej używać do argumentów metod, typów zwracanych itp., ponieważ nie ma inicjatora umożliwiającego zdefiniowanie typu. Przykład var (dla typu String):
var message = "Some message…..";
System.out.println(message);
var nie jest słowem kluczowym: jest to zasadniczo zastrzeżona nazwa typu, podobnie jak int . Korzyści z var są ogromne: deklaracje typów zajmują dużo uwagi, nie przynosząc żadnych korzyści, a ta funkcja pozwoli zaoszczędzić czas. Ale jednocześnie, jeśli zmienna jest uzyskiwana z długiego łańcucha metod, kod staje się mniej czytelny, ponieważ od razu nie jest jasne, jaki rodzaj obiektu się tam znajduje. Dedykowane dla tych, którzy chcą lepiej zapoznać się z tą funkcjonalnością:

Kompilator JIT (GraalVM)

Bez zbędnych ceregieli przypomnę, że po uruchomieniu komendy javac aplikacja Java jest kompilowana z kodu Java do kodu bajtowego JVM, który jest binarną reprezentacją aplikacji. Jednak zwykły procesor komputera nie może po prostu wykonać kodu bajtowego JVM. Aby Twój program JVM działał, potrzebujesz innego kompilatora dla tego kodu bajtowego, który jest konwertowany na kod maszynowy, z którego procesor może już korzystać. W porównaniu do javac ten kompilator jest znacznie bardziej złożony, ale generuje także kod maszynowy wyższej jakości. Obecnie OpenJDK zawiera maszynę wirtualną HotSpot, która z kolei ma dwa główne kompilatory JIT. Pierwszy, C1 ( kompilator klienta ), jest przeznaczony do działania z większą szybkością, ale cierpi na tym optymalizacja kodu. Drugi to C2 (kompilator serwera). Szybkość wykonywania cierpi, ale kod jest bardziej zoptymalizowany. Kiedy który z nich jest używany? C1 doskonale nadaje się do aplikacji komputerowych, gdzie długie przerwy w JIT są niepożądane, a C2 świetnie nadaje się do długotrwałych programów serwerowych, gdzie spędzanie większej ilości czasu na kompilacji jest całkiem znośne. Kompilacja wielopoziomowa ma miejsce, gdy kompilacja najpierw przechodzi przez C1, a wynik przechodzi przez C2 (używane w celu większej optymalizacji). GraalVM to projekt stworzony, aby całkowicie zastąpić HotSpot. Możemy myśleć o Graalu jako o kilku powiązanych projektach: nowy kompilator JIT dla HotSpot i nowa poliglota maszyna wirtualna. Osobliwością tego kompilatora JIT jest to, że jest napisany w Javie. Zaletą kompilatora Graala jest bezpieczeństwo, czyli nie awarie, ale wyjątki, a nie wycieki pamięci. Będziemy mieli także dobre wsparcie dla IDE i będziemy mogli korzystać z debugerów, profilerów czy innych wygodnych narzędzi. Ponadto kompilator może być niezależny od HotSpot i będzie w stanie stworzyć szybszą wersję siebie skompilowaną w JIT. Dla kopaczy:

Równolegle G1

Moduł zbierający śmieci G1 jest z pewnością fajny, nie ma co do tego wątpliwości, ale ma też słaby punkt: wykonuje pełny cykl GC w jednym wątku. W czasach, gdy do znalezienia nieużywanych obiektów potrzebna jest cała moc sprzętu, jaką możesz zgromadzić, jesteśmy ograniczeni do jednego wątku. Naprawiliśmy ten problem w Java 10. Teraz GC działa teraz ze wszystkimi zasobami, które do niego dodajemy (to znaczy staje się wielowątkowe). Aby to osiągnąć, twórcy języka poprawili izolację głównych źródeł od GC, tworząc ładny, przejrzysty interfejs dla GC. Twórcy tego uroku, OpenJDK, musieli specjalnie wyczyścić zrzut w kodzie, aby nie tylko maksymalnie uprościć tworzenie nowych GC, ale także umożliwić szybkie wyłączenie niepotrzebnych GC z zestawu. Jednym z głównych kryteriów sukcesu jest brak spadku prędkości operacyjnej po wszystkich tych ulepszeniach. Spójrzmy także: Inne innowacje:
  1. Wprowadzono przejrzysty interfejs zbierający śmieci. Poprawia to izolację kodu źródłowego od różnych modułów zbierających elementy bezużyteczne, umożliwiając szybką i bezbolesną integrację alternatywnych modułów zbierających;
  2. Łączenie źródeł JDK w jedno repozytorium;
  3. Kolekcje otrzymały nową metodę - copyOf (Collection) , która zwraca niezmienną kopię tej kolekcji;
  4. Opcja opcjonalna (i jej warianty) ma nową metodę .orElseThrow() ;
  5. Od teraz maszyny JVM mają świadomość, że działają w kontenerze Docker i będą pobierać konfigurację specyficzną dla kontenera, zamiast wysyłać zapytania do samego systemu operacyjnego.
Oto kilka materiałów zawierających bardziej szczegółowe wprowadzenie do Java 10: Kiedyś byłem bardzo zdezorientowany faktem, że niektóre wersje Javy nazywały się 1.x. Chciałbym, żeby było jasne: wersje Java starsze niż 9 miały po prostu inny schemat nazewnictwa. Na przykład Java 8 można również nazwać 1.8 , Java 5 - 1.5 itd. A teraz widzimy, że wraz z przejściem na wydania z Java 9 zmienił się również schemat nazewnictwa, a wersje Java nie są już poprzedzone prefiksem 1.x . To koniec pierwszej części: omówiliśmy nowe interesujące funkcje Java 8-10. Kontynuujmy naszą znajomość z nowościami w kolejnym poście .
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION