Що відбувалося з 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 :)

Похожие статьи:
Приходите на Java.io 3.0 — встречу для всех, кого интересует Java и всё, что так или иначе с ней связано! Третья встреча Java.io пройдет в три...
Розробників і технічних спеціалістів запрошують взяти участь у програмі «Startup School: Gen AI» від Google. Це безплатний практичний курс...
Упродовж останнього року більшість компаній намагалися повною мірою виконувати свої фінансові зобов’язання перед...
Продуктова ІТ-компанія MacPaw створює новий кібербезпековий підрозділ Moonlock. Його завданням буде розробка продуктів...
Новые версии HandBrake 1.0.0 Rust 1.14 FreeDOS 1.2 Kubernetes 1.5 Visual Studio Code 1.8 TypeScript 2.1 Ruby 2.4.0 OpenCV 3.2 Python 3.6.0 Qt Creator 4.2 Xen 4.8 PHP...
Яндекс.Метрика