Статьи

Java 9 с первого взгляда

Многим Java 9 может показаться вспомогательным выпуском, который продвигает проект Jigsaw, который не может быть реализован в Java 8. Но наряду с новой системой модулей в JDK и рядом внутренних изменений, связанных с ней, Java 9 приносит также количество новых интересных вещей для инструментов разработчика. Вот основные моменты:

  • JShell — в Java теперь есть встроенная оболочка;
  • новый API процессов — предоставляет множество возможностей для обработки процессов, отсутствующих по умолчанию в предыдущих выпусках;
  • установка G1 в качестве сборщика мусора по умолчанию — в Java 8 это был параллельный GC;
  • новый клиент HTTP / 2 — предоставляется классом jdk.incubator.httpclient.HttpClient и классами пакета jdk.incubator.httpclient ;
  • новый API обхода стека — предоставляет стандартный API для анализа и работы с трассировками стека Java;
  • новый API реактивного программирования — предоставляет стандартный механизм в классе JDK java.util.concurrent.Flow ;
  • Улучшения языка — это несколько незначительных улучшений языка, наиболее значительным из которых является возможность иметь закрытые методы в интерфейсах (например, для использования по умолчанию методов, представленных в Java 8). Ранее предложение о введении ключевого слова var в язык было перенесено для Java 10 (хотя изначально планировалось на 9). Дальнейшие улучшения идут вместе с проектом Coin (включая набор небольших языковых изменений);
  • Разное — улучшенный Javadoc, методы фабрики коллекций (такие как Set.of (…), Map.of (…), List.of (…)), улучшения потокового API, методы частного интерфейса, мультирелизные JAR-файлы и ряд других (включая улучшения параллелизма и безопасности), которые не рассматриваются в этой статье (любопытный читатель может ознакомиться с JEP, относящимися к Java 9, здесь: http://openjdk.java.net/projects/jdk9/ ).

Еще одна вещь, которая не была реализована в JDK 9, — это упаковка инструмента JMH и предоставление микробенчмарков JMH по умолчанию для использования приложениями — работа там еще продолжается…

Ломая монолит

Давайте рассмотрим, что у нас есть приложение до Java 9, которое экспортирует текст в разные форматы — текст, PDF или слово. Структура приложения следующая:

Интерфейс exporter.IExporter предоставляет общий интерфейс для различных экспортеров:

1
2
3
public interface IExporter {
    public void export(String text, ByteArrayOutputStream baos);   
}

Три конкретных реализации экспортеров следующие:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
public class PdfExporter implements IExporter {
    public void export(String text, ByteArrayOutputStream baos) {
        Document document = null;
        try {
            document = new Document();
            PdfWriter.getInstance(document, baos);
            document.open();
            document.add(new Paragraph(text));
            document.close();
        } catch (DocumentException e) {
            if (document != null) {
                document.close();
            }
        }
    }
}
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
public class WordExporter implements IExporter {
    public void export(String text, ByteArrayOutputStream baos) {
        XWPFDocument document = null;
        try {
            document = new XWPFDocument();
            XWPFParagraph paragraph = document.createParagraph();
            XWPFRun run = paragraph.createRun();
            run.setText(text);
            document.write(baos);
        } catch (IOException e) {
            try {
                if (document != null) {
                    document.close();
                }
            } catch (IOException e1) {
            }
            // some logging or proper error handling ...
        }
    }
}
1
2
3
4
5
6
7
8
9
public class TextExporter implements IExporter {
    public void export(String text, ByteArrayOutputStream baos) {
        try {
            baos.write(text.getBytes());
        } catch (IOException e) {
            // some logging or proper error handling ...
        }
    }
}

Классы exporter.Main являются точкой входа приложения и используют только класс PdfExporter, если могут иметь следующую реализацию:

1
2
3
4
5
6
7
8
public static void main(String[] args) throws IOException {
        PdfExporter exporter = new PdfExporter();
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        exporter.export("sample text", baos);
        FileOutputStream fos = new FileOutputStream("example.pdf");
        fos.write(baos.toByteArray());
        fos.close();
}

Для экспортеров PDF и Word нам нужны следующие зависимости (мы используем Maven для сборки приложения):

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
<dependencies>
        <dependency>
            <groupid>org.apache.poi</groupid>
            <artifactid>poi</artifactid>
            <version>3.16</version>
        </dependency>
        <dependency>
            <groupid>org.apache.poi</groupid>
            <artifactid>poi-ooxml</artifactid>
            <version>3.16</version>
        </dependency>
        <dependency>
            <groupid>com.lowagie</groupid>
            <artifactid>itext</artifactid>
            <version>2.1.7</version>
        </dependency>    
</dependencies>

Итак, во-первых, определите модули для вашего приложения и модули JDK, которые вы захотите использовать. Ваше первоначальное приложение имеет все классы JDK в пути к классам, но вы можете ограничить это в Java 9 только теми модулями JDK, которые вам нужны. Для нашего простого приложения мы используем только классы java.io из JDK, которые являются частью модуля java.base, который требуется для каждого Java-приложения. Никаких других модулей JDK для нашего простого приложения не требуется, поэтому мы можем использовать очень минималистичную версию JRE для нашего приложения.

Для реального применения мы можем легко получить четыре логических модуля:

  • экспортер — этот модуль предоставляет общий API для всех экспортеров (класс exporter.IExporter);
  • exporter.word — этот модуль обеспечивает экспортер Word;
  • exporter.pdf — этот модуль обеспечивает экспортер PDF;
  • exporter.text — этот модуль предоставляет экспортер текста;
  • exporter.demo — этот модуль содержит класс Main и обеспечивает точку входа нашего приложения.

Следующим шагом является добавление метаданных модуля для каждого определенного нами модуля. Для этого мы определяем файл module-info.java для каждого модуля следующим образом:

Метаданные модулей для каждого модуля имеют следующее содержание (обратите внимание, как три конкретных экспортера зависят от модуля API, и что нам не требуется модуль java.base, поскольку это неявно требуется):

1
2
3
module exporter {
    exports exporter;
}
1
2
3
4
5
module exporter.pdf {
    requires exporter;
    requires itext;
    exports exporter.pdf;
}
1
2
3
4
module exporter.text {
    requires exporter;
    exports exporter.text;
}
1
2
3
module exporter.word {
    requires exporter;
}
1
2
3
4
5
6
module exporter.demo {
    requires exporter;
    requires poi.ooxml;
    requires poi.ooxml.schemas;
    exports exporter.word;
}

Нам все еще нужно как-то ссылаться на сторонние библиотеки (IText и Apache POI) из модулей. Если бы эти библиотеки были модульной версией, мы могли бы просто загрузить и потребовать их в соответствующих прикладных модулях. Однако мы не можем сделать это предположение, и поэтому нам нужен способ ссылаться на них как на зависимости «как они есть». Для целей миграции платформа Java предоставляет механизм использования сторонних библиотек в качестве модулей Java. Если они находятся на пути к классам нашего приложения, они будут включены в глобальный «неназванный» модуль, и все открытые классы из библиотек будут видны нашим модулям приложения. С другой стороны, поскольку для модульного приложения введен новый путь к модулю, в котором указаны все модули приложения, на сторонние библиотеки также можно ссылаться из пути модуля, используя имя файла JAR (без расширения .jar) — это так называемые автоматические модули. Мы бы предпочли второй вариант, как более «модульный» подход к миграции. Теперь последнее, что нужно создать и запустить наше модульное приложение. Фактически у нас есть разные стратегии для структуры модулей: например, несколько модулей в одном проекте (тот, который у нас есть), один модуль на проект или даже вложенная структура модуля. Также мы можем разместить файл module-info.java где угодно в модуле, если это указано в процессе компиляции javac (следовательно, в корне каталога модуля).

На момент написания этой статьи Maven до сих пор не поддерживает создание модульных приложений, а также Java IDE (такие как Eclipse, IntelliJ и NetBeans) имеют частичную и экспериментальную поддержку для модулей Jigsaw. По этой причине мы покажем, как построить наше модульное приложение из командной строки. Для этого мы сначала создадим каталог модулей в исходном корне проекта. И соберите каждый из пяти модулей следующим образом (обратите внимание, что Java 9 также предоставляет параметр -module-source-path для javac, чтобы модули можно было компилировать сразу, но мы не будем использовать их для демонстрации того, как создавать модули отдельно) — нам нужно находиться в корневом каталоге проекта и иметь самую последнюю папку bin JDK 9 в PATH:

1
2
3
4
5
6
mkdir modules\exporter modules\exporter.pdf modules\exporter.word modules\exporter.text modules\exporter.demo
javac -d modules\exporter src\exporter\module-info.java src\exporter\IExporter.java
javac --module-path modules -d modules\exporter.text src\exporter\text\module-info.java src\exporter\text\TextExporter.java
javac --module-path modules;C:\Users\Martin\.m2\repository\com\lowagie\itext\2.1.7 -d modules\exporter.pdf src\exporter\pdf\module-info.java src\exporter\pdf\PdfExporter.java
javac -cp C:\Users\Martin\.m2\repository\org\apache\poi\poi\3.16\poi-3.16.jar --module-path modules;C:\Users\Martin\.m2\repository\org\apache\poi\poi-ooxml\3.16;C:\Users\Martin\.m2\repository\org\apache\poi\poi-ooxml-schemas\3.16 -d modules\exporter.word src\exporter\word\module-info.java src\exporter\word\WordExporter.java
javac --module-path modules -d modules\exporter.demo src\exporter\demo\module-info.java src\exporter\demo\Main.java

Два важных момента, чтобы сделать здесь. Сначала убедитесь, что вы исключили исходные файлы jar (если они есть) из каталогов Maven сторонних зависимостей, так как это может привести к тому, что компилятор будет думать, что у вас есть дубликаты jar-модулей в тех же каталогах (например, poi-3.16 и poi-3.16-sources). банка). Во-вторых, с автоматическими модулями вы можете экспортировать один и тот же пакет из разных библиотек (также называемых разделенными пакетами ), что недопустимо в Java 9. Это в случае с Apache POI: библиотеки poi и poi-ooxml, необходимые для нашего модуля exporter.word экспортируйте пакет org.apache.poi , который приводит к ошибке компиляции, если оба они включены как автоматические модули. Чтобы обойти эту проблему в нашем случае, мы включаем библиотеку poi в classpath, которая составляет так называемый глобальный безымянный модуль, который виден из автоматических модулей. Теперь, чтобы запустить демонстрационное приложение, вам нужно выполнить следующую команду, которая объединяет пути модулей / classpath, использованные для компиляции выше, и указывает на основной класс в качестве главной точки входа нашего приложения:

1
javac --module-path modules -d modules\exporter.demo src\exporter\demo\module-info.java src\exporter\demo\Main.java

В результате у вас должен быть файл example.pdf, сгенерированный в текущем каталоге.

Играя с Java Shell

JSHell REPL находится в каталоге bin JDK9, и вы можете просто запустить его, перейдя в этот каталог и введя jshell . Вы можете (повторно) определять и проверять переменные, импорт, методы и типы. Вы также можете сохранить текущий прогресс и загрузить его снова в JSHell. Существует также потрясающая поддержка автозаполнения для импорта, типов и методов. Чтобы проиллюстрировать основы JSHell, попробуйте следующий набор команд в JShell:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
2 + 3
public int sum(int a, int b) { return a + b; }
import java.util.logging
import java.util.logging.*
Logger x = null;
class z {}
/help
/imports
/vars
/methods
/env
/types
/list
/list -all
/save script.jsh
/open script.jsh
/exit

Взяв под контроль внешние процессы

До Java 9 вы могли использовать два метода для создания новых процессов — либо используя метод Runtime.getRuntime (). Exec (), либо класс java.lang.ProcessBuilder следующим образом:

1
2
3
Process p = Runtime.getRuntime().exec("cmd /c notepad");
ProcessBuilder pb = new ProcessBuilder("cmd", "/c", “notepad");
Process p = pb.start();</pre>

Это, однако, довольно ограниченно с точки зрения управления созданными процессами или проверки других внешних процессов в операционной системе хоста. По этой причине JDK 9 представляет ряд новых методов для класса java.lang.Process, с одной стороны, и новую утилиту, предоставляемую java.lang.ProcessHandle, которая позволяет манипулировать внешним процессом потоковым способом, с другой. Вот несколько описательных примеров нового API процесса:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
LOGGER.info("PID: " + process.pid());
        LOGGER.info("Number of children: " + process.children().count());
        LOGGER.info("Number of descendants: " + process.descendants().count());
         
        ProcessHandle.Info info = process.info();
        LOGGER.info("Process command: " + info.command());
        LOGGER.info("Info: " + info.toString());
         
//      ProcessHandle handle = process.toHandle();
         
        CompletableFuture<process> exitedFuture = process.onExit();
        exitedFuture.whenComplete((p, e) -> { LOGGER.info("Process exited ... ");});
        exitedFuture.get();</process></pre>
<pre class="brush: java;">ProcessHandle[] processes = ProcessHandle.allProcesses().filter((pHandle) -> { return pHandle.info().toString().contains(name); }).toArray(ProcessHandle[] :: new);
        for(ProcessHandle process : processes) {
            LOGGER.info("Process details: " + process.info().toString());
        }
         
        return processes;

Повышение производительности с G1

Сборщик мусора G1 не является новым, но представлен в JDK 7. Особенность G1 заключается в том, что он работает для отдельных областей в куче, а не для поколений, что является более детальным подходом к очистке мусора. Он направлен на то, чтобы убрать как можно больше мусора, соблюдая при этом ряд ограничений. Это коллектор с низкой паузой, который делает компромисс между высокой пропускной способностью и низким временем паузы. В JDK 9 он становится сборщиком мусора по умолчанию, заменяя параллельный GC, который является GC с более высокой пропускной способностью. Предположение, лежащее в основе этого изменения, заключается в том, что улучшение пользовательского опыта в результате меньшего времени паузы ГХ лучше, чем высокая производительность коллектора (как в случае с параллельным ГХ). Верно ли это предположение, или нет, это остается на усмотрение приложений — если кто-то не желает рисковать переходом к G1, он все равно может указать параллельный GC для JVM с -XX: -UseParallelGC

Готовимся к HTTP 2.0

Новый клиент HTTP 2.0 предоставляется классом jdk.incubator.http.HttpClient, который все еще находится в фазе инкубации (то есть может произойти больше изменений).

Ходить по трассе стека

Новый API проверки трассировки стека предоставляется классом java.lang.StackWalker . Он может быть использован для фильтрации и обработки информации трассировки стека в мелкозернистой манере с использованием потоковых операций.

Включая реактивное программирование

Приложения, которые хотят предоставить механизм публикации-подписки, соответствующий парадигме реактивного программирования, должны обеспечить реализацию класса java.util.concurrent.Flow . Один стандартный издатель, предоставляемый JDK, который реализует интерфейс Flow.Publisher, предоставляется классом java.util.concurrent.SubmissionPublisher .

Опубликовано на Java Code Geeks с разрешения Мартина Тошева, партнера нашей программы JCG . Смотреть оригинальную статью здесь: Java 9 с первого взгляда

Мнения, высказанные участниками Java Code Geeks, являются их собственными.