Pull to refresh

39 новых фич, которые будут доступны в Java 12

Reading time9 min
Views26K
Original author: Simon Ritter
Из замечательного интервью на Хабре: «Саймон Риттер — человек, который работал над Java с самого начала и продолжает делать это в роли заместителя технического директора Azul — компании, работающей над виртуальной машиной Zing JVM и одним из лучших сборщиков мусора, C4 (Continuously Concurrent Compacting Collector)»
Ниже — перевод его статьи о новых фичах JDK 12 и некоторых трудностях, с которыми вы можете столкнуться мигрируя на новую сборку.

Я написал несколько постов в блоге, в которых перечислены все изменения для каждого из последних выпусков Java (JDK 10, JDK 11). Сейчас я рассмотрю темную сторону JDK 12, сосредоточив внимание на некоторых подводных камнях, которые могут вызвать проблемы если вы захотите перенести приложение на эту версию.



В JDK 12 наименьшее количество новых функций из всех выпусков Java на сегодняшний день(я насчитал 109 в JDK 10 и 90 в JDK 11). Это не плохо — из-за релизных циклов некоторые версии будут содержать больше изменений, а некоторые — меньше.


Я разобью новые функции на очевидные логические области: язык Java, библиотеки, JVM и другие функции JDK.


Изменения в языке


Функцией, которую я(и я предполагаю, что многие другие люди) будут считать наиболее заметной в JDK 12, является новый оператор switch (JEP 325). Это также первое изменение языка, которое будет использовано в качестве функции для "предварительного просмотра". Идея "предварительного просмотра" была представлена ​​в начале 2018 года в рамках JEP 12. По сути, это способ включения бета-версий новых функций используя опции командной строки. Используя предварительный просмотр все еще возможно вносить изменения основанные на обратной связи с пользователем, и в крайнем случае полностью удалить функцию, если она не была принята должным образом. Ключевым моментом в функциях предварительного просмотра является то, что они не включены в спецификацию Java SE. Про новый switch есть очень хороший перевод на Хабре.
В JDK 12 switch стал выражением, которое оценивает свое "содержимое" для получения результата. Сразу поясню, что это не влияет на обратную совместимость, поэтому вам не нужно изменять какой-либо код, который использует switch в качестве оператора.


Я буду использовать пример из JEP, так как он простой и понятный:


Старый switch
int numLetters;
switch (day) {
 case MONDAY:
 case FRIDAY:
 case SUNDAY:
  numLetters = 6;
  break;
 case TUESDAY:
  numLetters = 7;
  break;
 case THURSDAY:
 case SATURDAY:
  numLetters = 8;
  break;
 case WEDNESDAY:
  numLetters = 9;
  break;
 default:
  throw new IllegalStateException("Huh? " + day);
}

Как видите, мы сопоставляем день недели с названием переменной day, потом присваиваем значение numLetters. Теперь, когда switch является оператором, мы можем сделать присвоение один раз (значительно уменьшая вероятность ошибочного кода), используя результат оператора switch:


int numLetters = switch (day) {
 case MONDAY, FRIDAY, SUNDAY -> 6;
 case TUESDAY -> 7;
 case THURSDAY, SATURDAY -> 8;
 case WEDNESDAY -> 9;
 default -> throw new IllegalStateException("Huh? " + day);
};

Вы быстро заметите два изменения в синтаксисе. Разработчики OpenJDK наткнулись на малоизвестную синтаксическую функцию, называемую «список через запятую». Также оператор лямбда-выражения -> упрощает возвращение значения. Вы все еще можете использовать break со значением, если вы действительно хотите этого. Есть несколько других деталей об этой функции, но, вероятно, легче прочитать JEP.


Библиотеки


Есть одно изменение, которое я считаю очень полезным. Также есть и ряд второстепенных.


teeing collector


В Streams API, как обычно, появился новый Collector, предоставляемый служебным классом Collectors. Новый коллектор можно получить с помощью метода teeing(). teeing-коллектор принимает три аргумента: два коллектора и бифункцию. Для понимания работы этого коллектора рекомендую эту статью на Хабре.
Чтобы понять, как он это делает, я нарисовал диаграмму:


image

Все значения из входного потока передаются в каждый коллектор. Результат каждого коллектора передается в качестве аргументов в BiFunction для и генерации окончательного результата.


Простым примером является вычисление среднего значения (да, я знаю, что уже есть коллекторы для этого, например averagingInt(), но это простой пример, чтобы помочь понять концепцию).


/* Assume Collectors is statically imported */
double average = Stream.of(1, 4, 2, 7, 4, 6, 5)
                        .collect(teeing(
                            summingDouble(i -> i), 
                            counting(),
                            (sum, n) -> sum / n)
                         );

Первый коллектор вычисляет сумму входного потока, а второй — количество элементов. BiFunction делит сумму на количество элементов, чтобы получить среднее значение.


java.io


InputStream skipNBytes(long n) — пропускает и отбрасывает ровно n байтов с входного потока InputStream. Если n равно нулю или меньше, байты не пропускаются.


java.lang


Появился новый пакет, java.lang.constant, который является частью API constant JVM, JEP 334.


Каждый файл класса Java имеет постоянный пул, в котором хранятся операнды для инструкций байт-кода в классе. Разработчикам сложно манипулировать файлами классов из-за проблем с загрузкой классов. API constant JVM предоставляет символические ссылочные типы для описания каждой формы константы (класса, загружаемой константы, MethodHandle, MethodHandle константы, MethodType константы).


Это также повлияло на несколько других классов. У всех следующих классов теперь есть describeConstable() метод:


  • Class
  • Double
  • Enum
  • Float
  • Integer
  • Long
  • String
  • MethodHandle
  • MethodType
  • VarHandle

Как британец, я нахожу это довольно забавным. Термин «Констебль»(Constable, describeConstable) используется с 11-го века, и именно так мы часто обращаемся к полицейским. Это также имя известного художника 18-го века, Джона Констебла. Это заставляет меня задуматься, будет ли в будущей версии метод describeTurner(). Очевидно, что в данном случае это сокращение Constant Table, не относящееся к сотруднику закона или пейзажисту.


Следующие классы теперь включают метод resolveConstantDesc():


  • Double
  • Enum.EnumDesc
  • Float
  • Integer
  • Long
  • String

java.lang.Character


Внутренние классы были обновлены, чтобы включить новые блоки Unicode. Мне всегда нравится видеть то, что нашли люди чтобы добавить в Unicode, вот несколько примеров:


  • Шахматные символы
  • Цифры майя
  • Согдийский — восточный иранский язык, который перестал использоваться в 11-ом веке.
  • Старый согдийский — более старый (и, я подозреваю, еще более ограниченный) вариант согдийского

java.lang.Class


arrayType() возвращает Class для типа массива, тип компонента которого описывается этим Class. Это можно проверить используя jshell:


jshell> (new String[2]).getClass().getName()
$11 ==> "[Ljava.lang.String;"
jshell> (new String[2]).getClass().arrayType()
$12 ==> class [[Ljava.lang.String;
jshell> "foo".getClass().arrayType()
$15 ==> class [Ljava.lang.String;

Я не совсем уверен, в чем смысл этого метода, так как все, что он делает — это добавляет Class к тому типу, который представляет этот класс.


componentType(), такой же, как и getComponentType(). Напрашивается вопрос — зачем добавлять избыточный метод?


descriptorString() — опять же, возвращает тот же результат, что и getName(). Однако он необходим, поскольку Class теперь реализует TypeDescriptor интерфейс, связанный с новым API constant JVM.


lava.lang.String


indent() — добавляет ряд начальных пробелов в строку. Если параметр отрицателен, то это количество начальных пробелов будет удалено(если это возможно).


transform() — Применяет предоставленную функцию к строке. Результат может не быть строкой.


java.lang.invoke


У VarHandle теперь есть toString() для возврата компактного описания.


У java.net.SecureCacheResponse и у java.net.ssl.HttpsConnection есть новый метод, getSSLSession() который возвращает Optional, содержащий SSLSession, используемый в соединении.


java.nio.files


У класса Files есть новый метод, mismatch(), который находит и возвращает позицию первого несовпадающего байта в содержимом двух файлов или -1L, если нет несоответствия.


java.text


Есть новый класс CompactNumberFormat. Это подкласс NumberFormat, который форматирует десятичное число в компактной форме. Примером компактной формы — 1M вместо 1000000, таким образом — требует два вместо девяти символов. NumberFormat и java.text.spi.NumberFormatProvider были расширены, чтобы включить новый метод getCompactNumberInstance(). Существует также новый enum, NumberFormatStyle который имеет два значения: LONG и SHORT.


java.util.concurrent


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


  • exceptionallyAsync
  • exceptionallyCompose
  • exceptionallyComposeAsync

Эти методы расширяют возможности создания нового CompletionStage из существующего, CompletionStage если текущий завершается исключением. Проверьте документацию API для деталей.


javax.crypto


Класс Cipher имеет новый метод toString(), который возвращает строку, содержащую преобразование, режим и поставщика Cipher.


javax.naming.ldap.spi


Это новый пакет в JDK 12 и он содержит два класса — LdapDnsProvider, который является классом поставщика услуг для поиска DNS при выполнении операций LDAP, и LdapDnsProviderResults который инкапсулирует результат поиска DNS для URL-адреса LDAP.


Swing


Swing все еще обновляется! Да, filechooser.FileSystemView теперь имеет новый метод getChooserShortcutPanelFiles(). Он возвращает массив файлов, представляющих значения для отображения по умолчанию на панели ярлыков выбора файлов.


Изменения в JVM


JEP 189: Shenandoah: Low-Pause-Time сборщик мусора


Shenandoah — это исследовательский проект, анонсированный Red Hat в 2014 году, который нацелен на требования приложений с малой задержкой для управления памятью в JVM. Его цели — максимальное время паузы 1..10мс для кучи более 20 Гб (поэтому он не предназначен для небольших приложений — как ответил один из разработчиков Shenandoah, это не так и он отлично справляется с небольшими приложениями). Этот коллектор спроектирован так, чтобы работать параллельно с потоками приложения, поэтому избегайте проблем, которые мы видим в большинстве сборщиков мусора.


JEP 344: смешанные коллекции G1


Это изменение предназначено для того, чтобы улучшить поведение сборщика G1 при достижении заданной цели задержки. G1 делит пространство кучи (как старое, так и старое) на регионы. Идея заключается в том, что в старом поколении не нужно собирать мусор за одну операцию. Когда G1 необходимо собрать мусор, он выбирает регионы, которые он определяет. Это называется набором сбора. До JDK 12, когда начиналась работа над набором все работы должны были быть завершены, по сути, как атомарная операция. Проблема заключалась в том, что иногда из-за изменений в использовании приложением пространства кучи, набор сбора оказывался слишком большим и занимал слишком много времени для сбора, что приводило к тому, что время паузы не достигалось.


В JDK 12, если G1 идентифицирует эту ситуацию, то прервет сбор данных на полпути, если это не повлияет на способность приложения продолжать выделять пространство для новых объектов. Чистый эффект G1 будет лучше при достижении небольшого времени паузы.


JEP 346: быстро вернуть неиспользованную выделенную память из G1


Это еще одно улучшение производительности для G1, но еще одно связано с тем, как JVM взаимодействует с остальной частью системы. Очевидно, что для кучи JVM требуется память, и при запуске она запрашивает память у распределителя виртуальной памяти операционной системы. При запуске приложения могут быть случаи, когда объем памяти, необходимый для кучи, падает, и часть выделенной памяти может быть возвращена операционной системе для использования другими приложениями.


G1 уже делает это, но может делать это только в одном из двух мест. Во-первых, во время полного сбора, а во-вторых, во время одного из параллельных циклов. G1 старается не делать полный сбор, и при низком использовании памяти между циклами сбора могут быть значительные периоды. Это приводит к тому, что G1 может сохранять зафиксированную память в течение длительного времени.


В JDK 12 G1 во время бездействия приложения будет периодически пытаться продолжить или запустить параллельный цикл для определения общего использования кучи Java. Неиспользованная память может быть возвращена операционной системе более своевременным и предсказуемым образом.


Новый флаг командной строки -XX:G1PeriodicGCInterval может быть использован для установки количества миллисекунд между проверками.


Эта функция приведет к более консервативному использованию памяти JVM для приложений, которые простаивают в течение длительных периодов времени.


Другие новые функции JDK


JEP 230: набор микробенчмаркинга


Java Microbenchmarking Harness (JMH) был разработан Алексеем Шипилевым, когда он работал в Oracle, и предоставляет обширную платформу для разработки тестов производительности для приложений Java. Алексей проделал выдающуюся работу, помогая людям избежать многих простых ошибок, которые они допускают, пытаясь проанализировать производительность приложений: прогревать, избегать исключений, и т. д.


Теперь JMH может быть включено в OpenJDK. Любой, кто интересуется работой над самим JDK и изменением кода, может использовать это для сравнения производительности как до, так и после их изменений, а также для сравнения производительности в разных выпусках. Ряд тестов включены, чтобы включить тестирование; Дизайн JMH таков, что легко добавлять новые тесты туда, где это необходимо.


JEP 340: один порт Aarch64, а не два


OpenJDK имеет два порта для архитектуры Arm64, один из которых предоставлен Oracle, а другой — Red Hat. Поскольку в этом не было необходимости, и Oracle перестала поддерживать Arm для своих двоичных файлов JDK, было принято решение использовать только порт Red Hat, который все еще поддерживается и развивается.


JEP 341: архивы CDS по умолчанию


Класс Data Sharing (CDS) раньше был коммерческой функцией в Oracle JDK. С недавним переходом, выполненным в JDK 11, для устранения всех функциональных различий между Oracle JDK и OpenJDK, его включили в OpenJDK.


Для использования CDS требуется архив, созданный для классов, которые загружаются при запуске приложения. В JDK 12 для 64-разрядных платформ теперь есть файл classes.jsa в каталоге lib/server. Это архив CDS для «классов по умолчанию». Я предполагаю, что он означает все открытые классы в модулях JDK; Я не мог найти способ распаковать его, чтобы проверить. Поскольку CDS включен по умолчанию, что эквивалентно -Xshare:auto опции в командной строке, пользователи получат выгоду от улучшенного времени запуска приложений из него.


Выводы


JDK 12 предоставляет небольшое количество новых функций и API, причем выражение switch является наиболее интересным для разработчиков. Пользователи G1, несомненно, оценят улучшения производительности.


С новой версией релиза я бы посоветовал всем пользователям тестировать свои приложения в этом выпуске. Отслеживание постепенных изменений позволит избежать неожиданностей, если вы решите перейти к следующему выпуску долгосрочной поддержки.


У нас есть бесплатные сборки JDK 12 для Zulu Community Edition, которые помогут вам в тестировании. Обязательно попробуйте их.

Tags:
Hubs:
+14
Comments17

Articles