JavaRush /Java блог /Random UA /З 8 до 13: повний огляд версій Java. Частина 1
Константин
36 рівень

З 8 до 13: повний огляд версій Java. Частина 1

Стаття з групи Random UA
Кошенята, всім привіт)) Отже, сьогодні у нас на подвір'ї 2020 рік, і до виходу Java 14 залишилося зовсім небагато. Готову версію варто чекати 17 березня, аналізувати, що там прийшло свіженького і цікавого вже постфактум, ну а сьогодні хотілося б освіжити пам'ять за попередніми версіями Java. Що нового вони нам принесли? Давайте подивимося. А почнемо огляд з Java 8, оскільки вона ще досить актуальна і користується в більшості проектів. З 8 до 13: повний огляд версій Java.  Частина 1 - 1Раніше нові версії виходабо раз на 3-5 років, але останнім часом у Oracle інший підхід - "нова Java раз на півроку". І ось кожні півроку ми спостерігаємо випуск фіч. Добре це чи погано – кожен бачить по-своєму. Наприклад мені це не дуже подобається, так як у нових версій не так вже й багато нових фіч, але при цьому версії ростуть, як гриби після дощу. Моргнув пару разів на проекті з Java 8, а там вже і Java 16 вийшла (зате коли вона виходить рідко, відбувається накопичення нових фіч, і в результаті ця подія довгоочікувана, як свято: всі обговорюють нові плюшки і ти ніяк не пройдеш повз це) . Тож давайте почнемо!

Java 8

Functional Interface

Що це? Функціональний інтерфейс - це інтерфейс, що містить один нереалізований (абстрактний) метод. @FunctionalInterface - необов'язкова інструкція, яка ставиться над таким інтерфейсом. Потрібна для перевірки того, чи він відповідає вимогам функціонального інтерфейсу (наявність лише одного абстрактного методу). Але, як завжди, у нас є деякі застереження: default і static методи не підпадають під ці вимоги. Тому може бути кілька таких методів + абстрактний, і інтерфейс буде функціональним. Також може містити методи класу Object, які впливають визначення інтерфейсу як функціонального. Додам пару слів про default та static методи:
  1. Методи з модифікатором default дозволяють додавати нові методи до інтерфейсів, не порушуючи їх існуючу реалізацію.

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

    Так-так, ми додаємо реалізований метод в інтерфейс, і при імплементації даного методу його можна не визначати, а використовувати як успадкований. Але якщо клас реалізує два інтерфейси з даним методом, у нас буде помилка компіляції, а якщо реалізує інтерфейси та успадковує клас з певним однаковим методом, метод класу батька перекриватиме методи інтерфейсу та ексепшен не вилізе.

  2. Методи static в інтерфейсі працюють так само, як і static методи класу. Не забуваємо: успадковувати static методи не можна, як не можна викликати і static метод із класу-спадкоємця.

Отже, ще кілька слів про функціональні інтерфейси і їдемо далі. Ось основні переліки ФІ (решта — це їх різновиди):

    Predicate - приймає аргументом деяке значення T, повертає boolean.

    Приклад:boolean someMethod(T t);

  • Consumer приймає аргумент типу Т, нічого не повертає (void).

    Приклад:void someMethod(T t);

  • Supplier – нічого не приймає на вхід, але повертає деяке значення T.

    Приклад:T someMethod();

  • Function – приймає на вхід параметр типу Т, повертає значення типу R.

    Приклад:R someMethod(T t);

  • UnaryOperator - приймає аргумент Т і повертає значення типу Т.

    Приклад:T someMethod(T t);

Stream

Стрими - це спосіб обробляти структури даних у функціональному стилі. Як правило, це колекції (але можна використовувати їх в інших, менш поширених ситуаціях). Більш зрозумілою мовою, Stream - це потік даних, який ми обробляємо працюючи з усіма даними одночасно, а не перебором, як при for-each. Давайте розглянемо невеликий приклад. Припустимо, у нас є набір чисел, які ми хочемо відфільтрувати (менше 50), збільшити на 5, і з тих, що залишабося, вивести в консоль перші 4 числа. Як би ми це перевірабо раніше:
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);

}
Начебто коду не так багато, а логіка вже трохи плутана. Подивимося, як це виглядатиме за допомогою стриму:
Stream.of(46, 34, 24, 93, 91, 1, 34, 94)

      .filter(x -> x < 50)

      .map(x -> x + 5)

      .limit(4)

      .forEach(System.out::print);
Stream-и сильно полегшують життя, скорочуючи обсяг коду і роблячи його більш читаним. Хто хоче більш детально заглибитися в цю тему, ось непогана (я б навіть сказав чудова) стаття на цю тему .

Lambda

Мабуть, найважливішою та довгоочікуваною фічею є поява лямбд. Що таке лямбда? Це блок коду, який можна передати в різні місця, тому він може бути виконаний пізніше стільки разів, скільки потрібно. Звучить досить заплутано, чи не так? Простіше кажучи, за допомогою лямбд можна реалізовувати метод функціонального інтерфейсу (така собі реалізація анонімного класу):
Runnable runnable = () -> { System.out.println("I'm running !");};

new Thread(runnable).start();
Як швидко і без зайвої тяганини ми реалізували метод run(). І так: Runnable – це функціональний інтерфейс. Також лямбди використовую у роботі зі стримами (як у прикладах зі стримами вище). Не будемо сильно заглиблюватися, так як можна пірнути досить глибоко, залишу пару посилань, щоб хлопці, хто в душі той ще копаток, міг "поколупатися" на ура:

foreach

У Java 8 з'явився новий foreach, який працює з потоком даних, як і стрим. Ось приклад:
List<Integer> someList = Arrays.asList(1, 3, 5, 7, 9);

someList.forEach(x -> System.out.println(x));
(аналог someList.stream().foreach(…))

Method reference

Посилальні методи - це новий корисний синтаксис, створений для посилання на існуючі методи або конструктори Java-класів або об'єктів через :: Посилання на методи бувають чотирьох видів:
  1. Посилання на конструктор:

    SomeObject obj = SomeObject::new

  2. Посилання на статичний метод:

    SomeObject::someStaticMethod

  3. Посилання на нестатичний метод об'єкта певного типу:

    SomeObject::someMethod

  4. Посилання на типовий (нестатичний) спосіб конкретного об'єкта

    obj::someMethod

Часто посилання на методи використовуються в стримах замість лямбд (посилальні методи швидше за лямбд, але поступаються в читання).
someList.stream()

        .map(String::toUpperCase)

      .forEach(System.out::println);
Для тих, хто хоче більше інформації за посиланнями:

API Time

З'явилася нова бібліотека для роботи з датами та часом – java.time. З 8 до 13: повний огляд версій Java.  Частина 1 - 2Новий API схожий на будь-який Joda-Time. Найбільш значущі розділи цього API:
  • LocalDate – це конкретна дата, як приклад – 2010-01-09;
  • LocalTime - час, що враховує часовий пояс - 19:45:55 (аналог LocalDate);
  • LocalDateTime - комбо LocalDate + LocalTime - 2020-01-04 15:37:47;
  • ZoneId - представляє часові пояси;
  • Clock — за допомогою цього типу можна достукатися до поточного часу та дати.
Ось пара справді цікавих статей на цю тему:

Optional

Це новий клас у пакеті java.util , обгортка (контейнер) для значень, фішкою якої є те, що вона також може безпечно містити null . Отримання optional: Optional<String> someOptional = Optional.of("Something"); Якщо в Optional.of передавати null , у нас впаде наше улюблене NullPointerException . Для таких випадків юзають: Optional<String> someOptional = Optional.ofNullable("Something"); - у цьому методі можна не боятися null. Далі, створення спочатку порожнього Optional: Optional<String> someOptional = Optional.empty(); Для перевірки, чи він порожній, юзаєм: someOptional.isPresent(); поверне нам true або false. Виконати певну дію, якщо значення є, і нічого не робити, якщо значення немає: someOptional.ifPresent(System.out::println); Зворотний метод, який повертає передане значення, якщо Optional порожній (такий собі запасний план): System.out.println(someOptional.orElse("Some default content")); Продовжувати можна дуже і дуже довго (благо, в Optional додали методів як із щедрою руки), все ж таки на цьому зупинятися не будемо. Краще я для затравки пару посилань залишу: Ми пробіглися найвідомішими нововведеннями в Java 8 — це далеко не все. Якщо ви хочете знати більше, це я залишив для вас:

Java 9

Отже, 21 вересня 2017 року світ побачив JDK 9. Ця Java 9 поставляється з багатим набором функцій. Хоча немає нових концепцій мови, нові API та діагностичні команди безперечно будуть цікаві розробникам. З 8 до 13: повний огляд версій Java.  Частина 1 - 4

JShell (REPL - read-eval-print loop)

Це реалізація Java інтерактивної консолі, яка використовується для тестування функціоналу і використання в консолі різних конструкцій, наприклад інтерфейсів, класів, enum, операторів і т.д. Для запуску JShell потрібно лише написати в терміналі – jshell. Далі можна писати все, що дозволить нам фантазія: З 8 до 13: повний огляд версій Java.  Частина 1 - 5За допомогою JShell можна створювати методи верхнього рівня та використовувати їх усередині тієї самої сесії. Методи будуть працювати, як і статичні методи, за винятком того, що ключове слово static можна пропустити Докладніше — у посібнику з Java 9. REPL (JShell) .

Private

Починаючи з 9 версії Java, у нас з'явилася можливість використовувати private методи в інтерфейсах (default і static методи, оскільки інші просто не можемо перевизначити через недостатній доступ). private static void someMethod(){} try-with-resources Було модернізовано можливість обробки винятків Try-With-Resources:
BufferedReader reader = new BufferedReader(new FileReader("....."));
  try (reader2) {
  ....
}

Модульність ( Jigsaw )

Модуль – це група взаємопов'язаних пакетів та ресурсів разом із новим файлом дескриптора модуля. Цей підхід використовується, щоб послабити зв'язаність коду. Ослаблення зв'язку - ключовий фактор для зручності підтримки та розширюваності коду. Модульність реалізується на різних рівнях:
  1. Мова програмування.
  2. Віртуальна машина.
  3. Стандартний java API.
JDK 9 поставляється з 92 модулями: ми можемо юзати їх чи створити свої. Ось пара посилань для глибшого ознайомлення:

Immutable Collection

У Java 9 стало можливим створення та заповнення колекції одним рядком, при цьому роблячи її immutable (раніше для створення immutable колекції нам потрібно було створити колекцію, заповнити її даними та виклик методу, наприклад — Collections.unmodifiableList). Приклад такого створення: List someList = List.of("first","second","third");

Інші нововведення:

  • розширено Optional (додані нові методи);
  • з'явабося інтерфейси ProcessHandle та ProcessHandle для керування діями операційної системи;
  • G1 - дефолтний збирач сміття;
  • HTTP клієнт з підтримкою як HTTP/2 протоколу та WebSocket;
  • розширений Stream;
  • додано фреймворк Reactive Streams API (для реактивного програмування);
Для більш повного занурення в Java 9 раджу почитати:

Java 10

Отже, через шість місяців після випуску Java 9 у березні 2018 року (пам'ятаю як учора), на сцену виходить Java 10. З 8 до 13: повний огляд версій Java.  Частина 1 - 6

var

Тепер ми можемо не надавати тип даних. Ми позначаємо повідомлення як var, і компілятор визначає тип повідомлення типу ініціалізатора, присутній праворуч. Дана фіча доступна тільки локальних змінних з ініціалізатором: її не можна використовувати для аргументів методів, типів даних, що повертаються і т.д., так як там немає ініціалізатора для можливості визначення типу. Приклад var (для типу String):
var message = "Some message…..";
System.out.println(message);
var не є ключовим словом: по суті, це зарезервоване ім'я типу, як і int . Користь від var велика: оголошення типів забирають на себе багато уваги, не приносячи жодної користі, а ця фіча заощадить час. Але в той же час, якщо змінна виходить з довгого ланцюжка способів, код стає менш читаним, тому що відразу неясно, що за об'єкт там лежить. Присвячується тим, хто хоче ближче познайомитися з цим функціоналом: З 8 до 13: повний огляд версій Java.  Частина 1 - 7

JIT-compiler (GraalVM)

Без зайвих передмов нагадаю: при запуску команди javac програма Java компілюється з Java-коду в байткод JVM, який є бінарним поданням програми. Але звичайний процесор комп'ютера не може просто так виконати байткод JVM. Для роботи вашої програми JVM потрібен ще один компілятор вже для цього байткоду, який перетворюється на машинний код, який процесор вже може використовувати. У порівнянні з javac цей компілятор набагато складніший, але і в результаті видає більш високоякісний машинний код. На даний момент OpenJDK містить віртуальну машину HotSpot, у якої, у свою чергу, є два основні JIT-компілятори. Перший - C1 ( клієнтський компілятор), створений для більш високошвидкісної роботи, але при цьому страждає оптимізація коду. Другий - C2 (серверний компілятор). Страждає швидкість виконання, але при цьому код більш оптимізований. Коли використовується який? С1 відмінно підходить для настільних додатків, де небажані довгі паузи JIT-компілятора, а С2 — для серверних програм, що довго працюють, в яких цілком непогано витратити більше часу на компіляцію. Багаторівнева компіляція - це коли спочатку компіляція проходить за допомогою С1, а результат проходить через С2 (використовується для більшої оптимізації). GraalVM- Це проект, створений для повної заміни HotSpot. Ми можемо розглядати Graal як кілька пов'язаних проектів: новий JIT-компілятор для HotSpot та нову віртуальну машину polyglot. Особливість цього JIT-компілятора полягає в тому, що він написаний на Java. Перевага Graal компілятора – безпека, тобто не збої, а винятки, а не витоку пам'яті. Також ми матимемо хорошу підтримку IDE, і ми зможемо юзати налагоджувачі, профільники або інші зручні інструменти. Крім цього, компілятор цілком може бути незалежним від HotSpot, і він зможе створювати швидшу JIT-скомпільовану версію самого себе. Копачам:

Паралельний G1

Складальник сміття G1 звичайно крутий, суперечки немає, але є і слабке місце: він виконує однопоточний повний цикл GC. У той час, коли вам потрібна вся потужність обладнання, яке ви можете зібрати для пошуку об'єктів, що не використовуються, ми обмежуємося одним потоком. У Java 10 це виправабо. Тепер GC тепер працює з усіма ресурсами, які ми до нього додаємо (тобто стає багатопотоковим). Для цього розробники мови покращабо ізоляцію основних вихідних джерел від GC, створивши для GC хороший чистий інтерфейс. Розробникам цієї милоты — OpenJDK, довелося безпосередньо розгребти звалище в коді, щоб максимально спростити створення нових GC, а й дати можливість швидко відключати непотрібні GC зі складання. Один із основних критеріїв успіху — відсутність просідання за швидкістю роботи після всіх цих поліпшень. Дивимося ще: Інші нововведення:
  1. Вводиться чистий інтерфейс збирача сміття (Garbage-Collector Interface). Завдяки цьому покращується ізоляція вихідного коду від різних збирачів сміття, даючи можливість інтегрувати альтернативні збирачі швидко та «безболісно»;
  2. Поєднання вихідників JDK в один репозиторій;
  3. Колекції отримали новий метод - copyOf (Collection) , який повертає постійну копію цієї колекції;
  4. Optional (і його різновиди) отримав новий метод .orElseThrow() ;
  5. Відтепер JVM знають про те, що вони запускаються в контейнері Docker, і витягуватимуть специфічну для контейнера конфігурацію, а не вимагатимуть саму операційну систему.
Ось ще кілька матеріалів для детальнішого ознайомлення з Java 10: Раніше мене дуже плутало те, що деякі версії Java називалися 1.x. хотілося б зробити певну ясність: версії Java до 9 просто мали іншу схему іменування. Наприклад, Java 8 також можуть називати 1.8 , Java 5 - 1.5 і т. д. А зараз ми бачимо, що з переходом на випуски з Java 9 схема назв також змінилася, і версії Java більше не мають префіксу 1.x. Ось і кінець першої частини: ми пройшлися новими цікавими фічами java 8-10. Давайте продовжимо наше знайомство з найсвіжішим у наступному пості .
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ