Прийшов час осідлати справжнього Буцефала🏇🏻Приборкай норовливого коня разом з Newxel🏇🏻Умови на сайті
×Закрыть

Що відбувалося з Java в останні роки. Огляд найважливіших нововведень

Привіт, я Володимир, Java-розробник в Perfectial, Java Lead в LITS і ментор на Cursor Education. Готуючись до доповіді на JavaDay Lviv 2020, я розбирав основні фічі, що з’явились в останніх версіях Java і які, на мою думку, важливо знати розробнику. Тепер вирішив поділитись інформацією у статті.

Як відомо, у вересні 2017 року архітектор Java-платформи Марк Рейнхольд запропонував змінити реліз-трейн: замість релізу кожних два (а то і більше) років, випускати новий реліз кожні півроку. На відміну від попередньої стратегії, коли версія не релізилась, поки не було готових запланованих JEP-ів, тепер в реліз йдуть лише готові. Усе недопрацьоване — чекає наступного релізу.

Local variable type inference

Перше, на що хочу звернути увагу, це Local variable type inference.

Поняття Type Inference не є новим. У Java 10 додали можливість використовувати це для локальних змінних. Тепер же, замість оголошення типу, можна написати слово «var» і компілятор сам визначить тип змінної.

Отже, код Object obj = new Object(); можна записати таким чином: var obj = new Object();

Сама назва jep-у говорить про те, що var — для локальних змінних. Для змінних класу і аргументів його використати не можна. Код:

var i = null; 
var i;
var func = () -> System.out.println("Hello world");

не буде компілюватись, а видасть помилку компіляції. У перших двох рядках компілятор просто не знатиме, який тип потірібно взати, а у третьому — результатом буде тип функціонального інтерфейсу, а не інтерфейс.

Ми не зможемо зберегти результат лямбда-виразу у змінну var, оскільки отримаємо сам тип функціонального інтерфейсу, а не інтерфейс. Маючи Local variable type inference, ми втарчаємо можливість використовувати поліморфізм, і наступний код видасть помилку, оскільки вказуємо тип ArrayList, а не List.

var list = new ArrayList<String> ();
list = new LinkedList<String> ();

При роботі з примітивами потрібно вказувати літерал, оскільки за замовчуванням відбувається неявне приведення типів до int:

var intNum = 42;       //  cast to int
var longNum = 42;      // cast to int
var doubleNum = 42;    // cast to int

Тому, щоб зберегти коректний тип, потрібно використовувати літерали:

var intNum = 42;       // cast to int
var longNum = 42L;     // cast to long   
var doubleNum = 42D;   // cast to double, value is  42.0
При роботі з примітивами потрібно вказувати літерал, оскілька за замовчуванням відбувається приведення типів до "int":

HttpClient

Однією з найцікавіших функцій, що з’явилась у Java 11 (якщо бути точним, то її додали ще у Java 9, але як інкубаційний модуль), є HttpClient.

До виходу класу HttpClient для роботи з http, в Java використовувався URLConnection, що створювало складнощі. Підтримки HTTP/2 не було, тому багато хто для роботи з http використовував зовнішні бібліотеки. HttpClient підтримує протокол HTTP/1.1 і HTTP/2 , синхронні і асинхронні моделі програмування, дає змогу отримувати body як reactive-stream.

HttpClient реалізований на основі патерну Builder.

var client = HttpClient.newBuilder()
      .version(Version.HTTP_2)
      .build();

Наступний код створить GET запит:

var request = HttpRequest.newBuilder()
      .uri(URI.create(«URL»)
      .GET()
      .build()

Щоб виконати запит var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

Тип response буде HttpResponse<String>

Http-клієнт не має функціоналу для пітримки form-data, тому це потрібно створювати вручну:

public static HttpRequest.BodyPublisher ofFormData(Map<Object, Object> data) {
        var builder = new StringBuilder();
        for (Map.Entry<Object, Object> entry : data.entrySet()) {
            if (builder.length() > 0) {
                builder.append("&");
            }
            builder.append(URLEncoder.encode(entry.getKey().toString(), StandardCharsets.UTF_8));
            builder.append("=");
            builder.append(URLEncoder.encode(entry.getValue().toString(), StandardCharsets.UTF_8));
        }
        return HttpRequest.BodyPublishers.ofString(builder.toString());
    }

Асинхронний запит виглядає наступним чином: var asyncResponse = httpClient. sendAsync(request, HttpResponse.BodyHandlers.ofString());

Тип змінної asyncResponse у цьому випадку буде CompletableFuture<HttpResponse<String>>.

API Updates

У Java 11 до класу String були додані нові методи:

String strip() повертає String, видаливши всі пробіли на початку і в кінці.

String stripLeading() повертає String, видаливши всі пробіли з лівої частини.

String stripTrailing() повертає String, видаливши всі пробіли з правої частини.

String isBlank() перевіряє, чи є String пустою без символів, табуляцій (окрім пробілів).

String isEmpty() повертає результат чи є String пустою без символів, табуляцій (окрім пробілів).

String repeat() повертає String задану кількість разів.

String lines() перетворює String y Stream з поділом: \n«, «\r», «\r\n».

Окрім класу String, нові методи додано і до інших класів.

Path of(String path) повертає Path за вказаною адресою.

Path of(URI uri) повертає Path за вказаним URI.

У класі Files з’явились статичні методи writeString і readString, що дозволяють просто записати чи прочитати String з заданого файлу.

Щоб записати String у файл text.txt:

var path = Path.of("text.txt");
Files.writeString(path, "Some text");

і для зчитування з файлу:

var path = Path.of("text.txt");

var text = Files.readString(path);

Predicate not(Predicate predicate): повертає предикат, що є запереченням заданого predicate.

Optional isEmpty(): повертає true, якщо optional є порожнім.

Цей метод зручний, коли при роботі з Optional є потреба перевіряти, чи Optional порожній чи ні. Для цього є метод optional.isPresent(), що повертає true, якщо optional не є порожнім.

У випадку, коли треба перевірити, чи optional є порожнім, можна без проблем написати !optional.isPresent().

return !userRepository
                .getAllByDepartmentId(id)
                .map(user -> modelMapper.map(user, UserDto.class))
                .filter(UserService::isUserHavePermissions)
                .isPresent();

У такому випадку втрачається читабельність коду і знак «!» можна не побачити і пропустити, тому використання isEmpty() у таких випадках дає нами кращу читабельність коду:

return userRepository
                .getAllByDepartmentId(id)
                .map(user -> modelMapper.map(user, UserDto.class))
                .filter(UserService::isUserHavePermissions)
                .isEmpty;

Collections toArray(Function function): приймає лямбда-вираз як аргумент, i за допомогою переданої function, перетворює колекцію у масив елементів.

var list = Arrays.asList(1, 2, 3, 4, 5);
Integer[] integers = list.toArray(Integer[]::new);

Окрім нових методів, у Java 11 видалили методи класу Thread:destroy() i stop(Throwable).

Більшість з нас «любить» switch-expression. У Java 12 він зазнав значних змін. Запустивши програму з прапорцем --enalved-preview, отримаємо новий switch. Тепер в switch є multiple case lable і можна писати код наступним чином:

    var result = switch (number) {
            case 1, 3, 5, 7, 9:
                break "not even";
            case 2, 4, 6, 8:
                break "even";
            default:
                break "zero";
        }

break тепер може повертати значення:

var result = switch (number) {

            case 1, 3, 5, 7, 9:
                break "not even";
            case 2, 4, 6, 8:
                break "even";
            default:
                break "zero";
        }

Можна написати код, використовуючи «arrow syntax»:

var result = switch (number) {
            case 1, 3, 5, 7, 9 -> “not even”;
            case 2, 4, 6, 8 ->  “even”;
            default -> “zero”;
        }

У Java 12 до класу String додано декілька нових методів.

String indent(int count): додає вказану в аргументах кількість пробілів перед стрінгою (якщо є \n) і додає і вкінці \n.

І в консолі отримаємо:

 Hi, Hello

Hi, Hello

   Hi, Hello

String transform(Function<? super String, ? extends R> f): приймає String як аргумент і R як результат.

char[] transform = template.transform(String::toCharArray);

Teeing collector

Функція Teeing Collector не була анонсована в офіційному JEP, а додана як мінорний change request.

Teeing collector повертає колектор, що складається з двох колекторів. Кожний елемент, переданий у результуючий колектор, опрацьовується двома колекторами, після чого вони змерджуються в один.

var result = Stream.of("Rob", "Max", "John", "Bob")
                    .collect(Collectors.teeing(
                        Collectors.filtering(n -> n.contains("o"), Collectors.toList()),
                        Collectors.filtering(n -> n.endsWith("ob"), Collectors.toList()),
                        (List<String> list1, List<String> list2) -> List.of(list1, list2)));

System.out.println(result);

І результатом буде: [[Rob, John, Bob], [Rob, Bob]]

Text blocks

Усім знайомий наступний код:

String loremIpsum = "Lorem ipsum dolor sit amet," +
        "consectetur adipiscing elit," +
        " sed do eiusmod tempor incididunt ut" +
        "labore et dolore magna aliqua.";

Код є не надто читабельним і зручним, тоді як у Scala i Kotlin є текстові блоки, що дозволяють записувати такий код зручніше. Text blocks у Java 13 є частиною майбутнього «Raw String Literals», що дозволяє писати і читати багаторядковий код набагато зручніше. Ця фіча давно підтримується у Scala, Kotlin, а тепер і в Java. Щоб зберегти багаторядковий String, раніше доводилось використовувати конкатенацію і літерал \n, а тепер все набагато простіше. Такий синтаксис має читабельний вигляд і записувати його набагато зручніше.

var s = """
            <html>
              <title>
                <p> Java is a top</p>
              </title>
              <body>
                <p> Text Block</p>
              </body>
            </html>
        """;
var day = switch (day) {
            case 1 -> numericString = "SUN";
            case 2 -> numericString = "MON";
            case 3 -> numericString = "THU";
            default -> {
                numericString = "N/A";
                System.out.println("Incorrect input");
                yield   "n/a";
            };

Новий switch в Java 13 є в статусі preview language feature, тобто за замовчуванням цей синтаксис не включений.

Dynamic CDS

Також варто згадати Dynamic CDS (Class Data Sharing) Archiver, що дозволяє запакувати найбільш використовувані класи в спеціальний архів, який можна завантажувати декількома JVM. Щоб завантажити класи, JVM виконує ряд операцій: зчитування класів та зберігання їх у внутрішніх структурах, пошук залежностей, перевірки над класом і т. д. У Java 5 додано CDS, який працює з bootstrap class loader.

У Java 10 додали CDS з префіксом Application, ідея якого розширити можливості вже існуючого CDS, включаючи в архів application класи.

Dynamic CDS покращує CDS таким чином, що він зможе створювати архіви при завершенні роботи програми, тобто класи, завантажені при роботі програми, будуть додані в архів.

P. S.

За декілька місяців має вийти реліз Java 14, що містить доволі цікаві JEP-и: HelpfulNullPointerExceptions, Records, Pattern Matching for Instanceof, second preview of Text Blocks. Зі зміною реліз-трейну нові фічі почали виходити набагато швидше, що говорить про те, що Java never die :)

LinkedIn

Похожие статьи

41 комментарий

Подписаться на комментарииОтписаться от комментариев Комментарии могут оставлять только пользователи с подтвержденными аккаунтами.

Дякую за статтю, але хотілося б більше практики, наприклад, як ви застосовували ці фітчи в своїх проектах

В тему — тут є трохи даних по тому що зараз використовується в джаві jug.lviv.ua/2020/01/17/surv

Наступний код створить GET запит:
var request = HttpRequest.newBuilder()
.uri(URI.create("URL«)
.GET()
.build()
Щоб виконати запит var response = httpClient.send(request, HttpResponse.BodyHandlers.ofString());

Многие и не догадываются, что происходит внутри при вызове метода send, якобы ожидая синхронный вызов. Поэтому могут взять и скопипастить кусок кода.
Но на самом деле, заглянув внутрь, можно придти к пониманию, что на самом деле метод send вызывает sendAsync, который использует дэфолтный cached thread pool, а он, в свою очередь, может «раздуться» большим количеством тредов. Поэтому, для синхронных методов нужно также оверрайдить thread pool при создании инстанса HTTP клиента.
Еще пример, подэфолту keep-alive законфижен на 20 мин и чтобы это значение переопределить — API нет, нужно оверрайдить проперти «jdk.httpclient.keepalive.timeout».
Еще есть кейс, касательно использования прокси (не паттерн :D) для реквестов, там тоже проблема и безумно неудобно предоставить один какой-то свой прокси для пачки реквестов.

Для простых реквестов все легко заведется и будет работать, но для прода — будьте внимательны и изучите «внутрянки», чтобы не было каких либо проблем.

String strip() повертає String, видаливши всі пробіли на початку і в кінці.

Раз уж решили упомянуть этот метод, то следует указать на его отличие от String.trim(), а именно, на работу с Unicode. 
System.out.println("\u2000 a b c \u2000".trim())  - вернёт " a b c «(c пробелами в начале и конце),
System.out.println("\u2000 a b c \u2000".strip()) - «a b c» (без пробелов)

var func = () -> System.out.println("Hello world");
не буде компілюватись, а видасть помилку компіляції. результатом буде тип функціонального інтерфейсу, а не інтерфейс.

.
При попытке скомпилировать var func = () -> System.out.println("Hello world")компилятор ругнётся на: " java: cannot infer type for local variable func (lambda expression needs an explicit target-type)". Лямбде нужен тип, который она обычно и берёт из определения переменной.

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

Как вы тогда объясните то что

      var func =  new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer integer) {
                return null;
            }
        };

ошибки компиляции не выдаст?

new Function<Integer, Integer>() { это уже не lamda a Anonymous Class,
причем если попытаться присвоить func повторно

<code>
func =  new Function<Integer, Integer>() {
            @Override
            public Integer apply(Integer integer) {
                return 10;
            }
        };

</code>
, вы получите ошибку компиляции
это уже не lamda a Anonymous Class,

Спасиб, что б я без вас делал )) Вы, наверное, не в курсе, что до появления лямбд их место занимали(а в некоторых случаях и сейчас занимают) анонимные классы?

Дмитрий, я не очень понимаю вашего сарказма. В предыдущем посте вы спросили почему var func, присвоенное new Function, не выдаёт ошибку компиляции. Я ответил на этот вопрос — это уже не lambda — Anonymous Class, в этом случае тип известен заранее.

Отсылка была к словам автора

не буде компілюватись, а видасть помилку компіляції. результатом буде тип функціонального інтерфейсу, а не інтерфейс.

, я указал на то что проблема не в этом, привёл для сравнения анлогичный код, но с явным типом. Читайте комментарий полностью, вопрос не звучал как «почему не выдаёт это оишбку», это был вопрос «почему не выдаёт ошибку, если следовать вашей теории?»

причем если попытаться присвоить func повторно

func =  new Function<Integer, Integer>() {             @Override             public Integer apply(Integer integer) {                 return 10;             }         };
, вы получите ошибку компиляции

Ествественно, так как это уже будет другая реализация данного интерфейса

Dynamic CDS (Class Data Sharing) Archiver, що дозволяє запакувати найбільш використовувані класи в спеціальний архів, який можна завантажувати декількома JVM

Правильнее будет сказать не «несколькими JVM» а «разными инстанцами одной JVM». Всё таки JVM здесь одна(HotSpot).

С http какаято фигня, кто бы не написал очередной хттп-клиент, его api и юзабилити все равно отвратительные :(

ну и какбы это далеко не все что с джавой случилось, а про методы в стринге и опшнале не писал только ленивый )

Не может быть, в Java var добавили?!

return userRepository
.getAllByDepartmentId(id)
.map(user -> modelMapper.map(user, UserDto.class))
.filter(UserService::isUserHavePermissions)
.isEmpty;

у Вас ошибка, isEmpty — это метод.

Программисты на C# читают, недоумевают и сочувствуют джавистам ;)

Не могу судить обо всех фичах и возможностях. Но описанное в статье уже было в С# лет 10 назад.

С# лет 10 назад

2007 год — C# 3.0

В дотнете поддержка модулей (assembly) были изначально — 2002 г., в джаве поддержку модулей добавили в 2017г., и, как всегда, недоделанная.

Value types в дотнете были изначально, в джаве до сих пор разрабатывают эту фичу — уже сколько, 10 лет?

Лямбды в джаве поддерживаются с 8 версии (2014), в C# поддерживаются с версии 3.0 (2007).

И т. д.

и сочувствуют джавистам ;)

И да и нет. Джава, это один из. Есть scala, kotlin... Если сравнивать платформы (jvm/.net), экосистему, то, думаю, .net будет не на первом месте.

Если сравнивать платформы (jvm/.net), экосистему, то, думаю, .net будет не на первом месте.

можна кілька прикладів у чому .net платформа наразі поступається jvm?

ну Зе теж більшість обрала. То й шо?

Платформа и экосистема. Вот несколько примеров:

* gc под jvm в разы лучше, чем под дотнетом
* неплохая реализация non-blocking io
* следующие проекты были написаны под jvm:
* hadoop (java)
* apache spark (scala)
* kafka (scala)
* akka (scala), есть порт под дотнет
* cassandra (java)
* фреймворки под микросервисы:
* spring cloud (java)
* vert.x (multilang)
* lagom (scala/java)
* большой выбор веб-серверов (с полноценной поддержкой http 2, non-blocking io)
* куча мелких либ почти под любую задачу и на любой вкус

* gc под jvm в разы лучше, чем под дотнетом

Почему тогда managed код в .net быстрее?

benchmarksgame-team.pages.debian.net/...​sgame/fastest/csharp.html

Ну и в частности

binary-trees
Background
A simplistic adaptation of Hans Boehm’s GCBench, which in turn was adapted from a benchmark by John Ellis and Pete Kovac.
Thanks to Christophe Troestler and Einar Karttunen for help with this benchmark.
Variance
When possible, use default GC; otherwise use per node allocation or use a library memory pool.
As a practical matter, the myriad ways to tune GC will not be accepted.
As a practical matter, the myriad ways to custom allocate memory will not be accepted.
Please don’t implement your own custom „arena” or „memory pool” or „free list” — they will not be accepted.

C# .NET Core
5.74
Java
8.32

Этот бенчмарк не о чем, это во первых. А во вторых, покажите мне бенчмарк, который тестирует работу gc.

Если вам похоливарить, то это без меня. Если хотите реально разобраться, то сначала вникните в матчасть:

en: www.youtube.com/watch?v=VaWgOCDBxYw
ru: www.youtube.com/watch?v=8pMfUopQ9Es

Ясно понятно, еще один свидетель секты java

если бы я хотел похоливарить я бы ответил на остальные пункты в списке твоих тезисов, которые померять нельзя.
«java быстрее, но это померять нельзя» — это уже сектанство невыличимое.

* gc под jvm в разы лучше, чем под дотнетом
* неплохая реализация non-blocking io
* куча мелких либ почти под любую задачу и на любой вкус

— досить спірні моменти

* следующие проекты были написаны под jvm:
* фреймворки под микросервисы:

Цікаво. Дякую. Для багатьох проектів (на жаль, не для всіх, або може я не в курсі) існують аналоги у .net екосистемі.

Цікаво. Дякую. Для багатьох проектів (на жаль, не для всіх, або може я не в курсі) існують аналоги у .net екосистемі.

Давно не слежу за развитием экосистемы дотнета. Можете привести аналоги — было бы интересно посмотреть на них.

* неплохая реализация non-blocking io

расскажите подробнее об этом. разве IO задачи не лежат на самой OS, которые делает загрузку?

или речь о Java NIO? так как в этом случае есть Pipelines — docs.microsoft.com/...​net/standard/io/pipelines, которые довольно близки по смыслу

так как в этом случае есть Pipelines — docs.microsoft.com/...​net/standard/io/pipelines, которые довольно близки по смыслу

Интересная вещь. Как я понимаю, появилась недавно? В 2019? И что там вызывается в реализация под линуксом?

в 2018-ом вышла первая версия. под линуксом у них был libuv, и так же план переписать на полностью managed реализацию, когда она будет не уступать libuv по производительности. не знаю насколько этот план произошёл уже или нет

И что там вызывается в реализация под линуксом?

.net managed sockets поверх epoll

Но это просто сокеты, pipelines о другом — высокоуровневое неаллоцирующее апи поверх byte стримов с буферами, мем пуллингом , threshold/backpressurre и прочими стандартными штуками для этих вещей. А io в .net/.net core и так изначально неблокирующий везде.

Как дела в java с неаллоицирующим апи кстати? а неблокирующее io — до сих пор на коллбеках небось?

HelpfulNullPointerExceptions

не минуло й 20 років.

Задумався: якщо «пробіл» = «whitespace» (принаймні, у даній статті), то як тоді перекласти «space» (U+0020)?

А «spacebar» (той, що на клавіатурі) ?

Подписаться на комментарии