Пользователь Константин
Константин
36 уровень
Одесса

Из 8 в 13: полный обзор версий Java. Часть 1

Статья из группы Java Developer
Котята, всем привет)) Итак, сегодня у нас на дворе 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 — это далеко не всё. Если вы хотите знать больше, то это я оставил для вас: Из 8 в 13: полный обзор версий Java. Часть 1 - 3

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. Давайте продолжим наше знакомство с самым свеженьким в следующем посте.
Комментарии (8)
Чтобы просмотреть все комментарии или оставить свой,
перейдите в полную версию
Andrei 41 уровень, Рига
23 февраля 2020
Спасибо автору за старания, но статья получилась сыроватой, очень много ляпов. Для новичков, которые вообще не в теме - сойдет лишь для того чтбы понять как там всего много и сколько незнакомых слов.
Vitaly Khan 40 уровень Master
19 февраля 2020
LocalTime — время, учитывающее часовой пояс LocalTime не содержит информацию о часовом поясе.
Vitaly Khan 40 уровень Master
19 февраля 2020
Optioanal - это сильно)))
Юрий 29 уровень, Калининград
18 февраля 2020
Супер
Interstellar 36 уровень, Воронеж Expert
18 февраля 2020
Отлично! В закладки. С 11 по 13 в следующей статье?
Сергей 11 уровень, Ekaterinburg
18 февраля 2020
Спасибо. Очень интересный обзор.
Mykhailo Ryzak 31 уровень
17 февраля 2020
Умереть не встать - сколько нужно еще прочитать!!!