26 February 2018

Изменения в стандартной библиотеке Java 10

Java

Недавно состоялся релиз-кандидат Java 10, и на Хабре вышла статья, где перечислены JEP'ы, вошедшие в новый релиз. Однако не все изменения в Java получают свой JEP. В частности, небольшие дополнения стандартной библиотеки практически всегда происходят без JEP'ов. При этом частенько практической пользы от них даже больше, чем от более серьёзных изменений, которые удостоились JEP'а. Давайте посмотрим, что добавилось в стандартную библиотеку Java 10.


Модуль java.base


JDK-8183743 add overloads that take a Charset parameter


Довольно часто приходится преобразовывать последовательности символов в последовательности байт и назад в том или ином виде, для чего приходится указывать кодировку. Исторически кодировка указывалась в виде текстовой строки (например, "UTF-8"). При этом любой метод, который принимал такую строку, может кинуть UnsupportedEncodingException, ведь мало ли что вы можете передать в строке. Однако чаще всего вас волнует только одна кодировка, которая точно всегда есть (вроде той же UTF-8). А исключение, как назло, проверяемое. Что в таком случае делать в обработчике исключения — многим не очень ясно. В результате появляются catch-блоки, которые не несут смысла и загрязняют код. Да и вообще строковые литералы посреди кода — это не самая лучшая идея.


Начиная с Java 7 ситуация меняется. Во-первых, появился класс StandardCharsets с константами вроде UTF_8, US_ASCII и т. д. Поддержка соответствующих кодировок гарантируется любой реализацией Java на любой платформе. Во-вторых, потихоньку стали появляться парные методы, принимающие вместо строки объект Charset. Этот процесс продолжили в Java 10, добавив новые конструкторы в классы PrintWriter, PrintStream, Formatter и Scanner, а также новые перегруженные методы ByteArrayOutputStream.toString(Charset) (да!), URLEncoder.encode(String, Charset) (да!!), URLDecoder.decode(String, Charset) (да!!!). Ещё добавились newReader(ReadableByteChannel, Charset) и newWriter(WritableByteChannel, Charset) в класс Channels, а также Properties.storeToXML(OutputStream, String, Charset). У ряда методов проверяемые исключения в сигнатуре вообще исчезли, что значительно упростит их использование (в том числе в лямбда-выражениях).


JDK-8191706 Add Reader::transferTo(Writer)


В Java 9 появился метод InputStream.transferTo(OutputStream), позволяющий легко перелить содержимое InputStream в OutputStream. Логичное продолжение этого — метод Reader.transferTo(Writer), делающий то же самое для символьных потоков.


JDK-8192833 Time-Based Release Versioning


В рамках перехода на релизы через равные промежутки времени обновили класс Runtime.Version, появившийся в Java 9. Если кто-то пропустил, теперь это официальный способ работы с версиями Java — синтаксический разбор строки версии, сравнение версий и т. д. В девятке сильно переработали формат и значение компонент версии, остановившись на major.minor.security в основной части. Например, если у вас Java 9.0.1, то 9 — это мажорная версия, 0 — минорная, а 1 — обновление безопасности. Это отразилось в названиях методов класса. В десятке решили, что идеал всё-таки не достигнут и смысл будет другой: feature.interim.update.patch (если patch нету, то он считается 0). В результате старые методы помечены устаревшими, хотя прожили всего полгода, и появились новые — feature(), interim(), update(), patch().


JDK-8186050 StackFrame should provide the method signature


Развитие ещё одного API, появившегося в Java 9 — StackWalker. Появился метод StackFrame.getMethodType(), который возвращает объект MethodType из java.lang.invoke API и StackFrame.getDescriptor(), возвращающий дескриптор метода в формате виртуальной машины. Теперь вы можете различить во фреймах методы с одинаковым именем, но разной сигнатурой. До сих пор это можно было сделать только по getLineNumber(), что ненадёжно и работает только при наличии отладочной информации.


JDK-8185993 MethodHandle.invokeWithArguments jumbo-arity


Это довольно важное внутреннее улучшение API java.lang.invoke в рамках реализации Constant Dynamic. Теперь метод MethodHandle.invokeWithArguments может принимать любое количество аргументов (а не только 251), если целевой метод является vararg-методом (с переменным числом аргументов). В рамках работы над ним появился ещё один вспомогательный метод MethodType.lastParameterType(), возвращающий тип последнего параметра (как раз параметр с типом-многоточием вроде String... в случае vararg-метода). Конечно, lastParameterType может пригодиться и в других случаях, если вы работаете с java.lang.invoke.


JDK-8164900 Add support for O_DIRECT


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


Изначально для поддержки этого режима предлагалось ввести новые конструкторы FileInputStream/FileOutputStream, но в процессе обсуждения пришли к новой константе O_DIRECT в com.sun.nio.file.ExtendedOpenOption. Да, да, com.sun! Это официально неподдерживаемый класс (он в модуле jdk.unsupported). Удивительно, что в таком пакете появляются новые фичи. А в модуле java.base появился метод FileStore.getBlockSize(), возвращающий размер блока на устройстве. Эта информация весьма полезна, если вы сами организуете кэшированный доступ к файлу.


JDK-8176841 Additional Unicode Language-Tag Extensions


В обновлённом стандарте Unicode строка locale может кодировать не только такие привычные вещи как язык или использование десятичной точки вместо запятой, но ещё и кучу всего, например, часовой пояс, валюту или какой день недели считать первым. Java теперь тоже поддерживает (и это, кстати, JEP!) Потребовались небольшие изменения в API. В частности, в класс LocaleNameProvider добавились методы getDisplayUnicodeExtensionKey и getDisplayUnicodeExtensionType для человеческого вывода информации об этих расширениях в данной локали.


JDK-8191349 Add java.time.format.DateTimeFormatter localizedBy(locale) method to reflect Unicode extensions


Взаимодействие новых расширений юникода с DateTimeFormatter привело к интересному эффекту. Там уже был метод withLocale, который просто задавал формат вывода.
А тут выясняется, что объект Locale может и часовой пояс переключить. Как следствие, например, withZone(zone).withLocale(locale) и withLocale(locale).withZone(zone) может создать разный форматтер. Решили, что если withLocale сможет молча переключить часовой пояс внутри форматтера, это будет слишком неожиданно, поэтому существующий метод решили не менять (он отвечает по-прежнему только за язык, формат представления чисел и т. д.). Вместо этого добавили новый метод — localizedBy, который уже умеет переключать и часовой пояс, и первый день недели.


JDK-8178117 public state constructors for Int/Long/DoubleSummaryStatistics


Вместе со Stream API в Java 8 появились классы вроде IntSummaryStatistics для подсчёта количества, суммы, минимального, максимального и среднего значения одновременно. Как выяснилось, совсем не просто восстановить состояние такого объекта, если вам по каким-то причинам надо продолжить набор статистики с определённого места. В Java 10 этот недосмотр исправлен: появились конструкторы вроде IntSummaryStatistics(long count, int min, int max, long sum), позволяющие восстановить состояние.


JDK-8177290 add copy factory methods for unmodifiable List, Set, Map


Эти изменения, наверно, окажутся полезнее всего. Напомню, что в Java 9 появились методы для создания неизменяемых коллекций вроде List.of(...), позволяющие сконструировать список и множество из явного набора элементов или массива. В Java 10 добавлено новое семейство методов — List.copyOf, Set.copyOf и Map.copyOf, которые конструируют неизменяемую коллекцию из коллекции. Разумеется, если на вход уже была подана неизменяемая коллекция, то лишнее копирование производиться не будет. Пользователям библиотеки Guava этот подход давно знаком: такой метод стоит вызывать, отдавая коллекцию в недоверенные руки, либо сохраняя долговременно в поле, чтобы случайно не изменить её.


Также это изменение добавляет новые коллекторы в Stream API: toUnmodifiableList, toUnmodifiableSet и toUnmodifiableMap. Любопытно, что у ранее существовавших коллекторов toList, toSet и toMap никто не гарантировал изменяемость. Поэтому можно было, не нарушая спецификацию, просто их поменять, возвращая неизменяемую коллекцию. Однако, разумеется, если позволить людям что-то делать (в данном случае менять коллекцию), многие сразу заложатся на это поведение. Сейчас уже поменять коллекторы вроде toList на неизменяемые означало бы сломать много кода. Можно было бы сказать, что авторы этого кода сами виноваты, но всё же для Java совместимость — это краеугольный камень.


Обратите внимание, что и методы copyOf, и новые коллекторы не терпят null'ы в качестве элементов коллекции (для Map — и в ключах, и в значениях). Поэтому слепо впихивать новые методы в существующий код может быть опасно.


JDK-8140281 (opt) add no-arg orElseThrow() as preferred alternative to get()


Несколько странная штука, на мой взгляд. Стюарт Маркс, автор класса Optional, давно недоволен тем, что люди слишком злоупотребляют методом get(), хотя предполагается его использование скорее в исключительных случаях, чем повсеместно. Ещё когда не вышла девятка Стюарт предлагал задепрекейтить его и заменить на какой-нибудь getWhenPresent(), чтобы у людей меньше тянулись руки его вызывать (длинное имя всегда нравится меньше). Тогда он встретил серьёзное сопротивление сообщества, но не бросил своих попыток. Окончательное решение — добавить новый метод orElseThrow() (без параметров), идентичный по смыслу методу get() и при этом get() не депрекейтить, а добавить просто примечание, что желательно использовать новый метод. В принципе orElseThrow() без параметров выглядит логично: это как orElseThrow с параметром, только кидаем некоторое дефолтное исключение. Однако, на мой взгляд, это добавит ещё больше бардака, потому что часть людей будет использовать старый метод, а часть — новый.


JDK-8188047 Add SplittableRandom.nextBytes


В Java 8 появился класс SplittableRandom — новый генератор псевдослучайных чисел, который быстрее Random, не синхронизируется, выдаёт значительно более качественные псевдослучайные числа и умеет делиться на два генератора, что полезно для параллельных стримов. Любопытно, что он не расширяет класс Random, как, например, SecureRandom или ThreadLocalRandom, что снижает его полезность: многие API уже принимают Random аргументом. При этом большинство методов у SplittableRandom и Random совпадают. Эта проблема ещё ждёт своего решения, а в Java 10 исправили маленькую недоработку: отсутствовал метод SplittableRandom.nextBytes, хотя аналог у Random имеется.


JDK-8187941 Add StampedLock stamp inspection methods


StampedLock — довольно интересный примитив синхронизации, позволяющий, например, избежать блокировок во многих случаях при синхронном чтении нескольких полей и создавать честную блокировку только при интенсивном конкурентном обновлении. Идея довольно проста: перед операцией вам выдаётся штамп, затем вы читаете поля и проверяете штамп. Если он не протух, значит всё хорошо, никто не пытался параллельно обновить и прочитанные поля действительно соответствуют атомарному состоянию. Иначе вы можете попытаться снова или уйти в обычную блокировку. Для оптимизации некоторых сценариев использования в него добавили новые методы, которые позволяют проверить, что за штамп у нас в руках: isWriteLockStamp, isReadLockStamp, isLockStamp, isOptimisticReadStamp.


JDK-8189611 JarFile versioned stream and real name support


Ещё одна доработка большой фичи, появившейся в Java 9: Multi-Release JARs. Напомню, это возможность в одном JAR-файле иметь несколько версий одного и того же класса или ресурса, соответствующих разной версии Java. При разработке этой функциональности, разумеется, обновили классы JarFile и JarEntry. Сейчас добавили два новых метода — это JarEntry.getRealName(), который возвращает настоящее имя файла (например, не foo, а META-INF/versions/9/foo) и JarFile.versionedStream(), который возвращает стрим записей JAR-файла, относящихся только к текущей версии.


Модуль java.desktop


К сожалению, стандартная библиотека для десктопа практически не развивается, изменения только косметические.


JDK-8183518 Premature deprecation of Event/InputEvent/KeyEvent in Java 9


В Java 9 задепрекейтили некоторые классы вроде Event. Однако оказалось, что не всё можно сделать без их использования. В частности Toolkit.getMenuShortcutKeyMask возвращает битовую маску с использованием констант из класса Event. Теперь добавили современную альтернативу — Toolkit.getMenuShortcutKeyMaskEx().


JDK-8186364 RFE: API for java.awt.geom.Path2D storage trimming


Path2D — это по большому счёту изменяемый список (а-ля ArrayList) двумерных точек с указанием способа соединения между ними (отрезок прямой, квадратичный или кубический сплайн). Как и в ArrayList разумно иметь метод trimToSize, который позволит урезать внутренний массив до реального количества точек, сэкономив память, если вы планируете долго хранить данный путь без изменения. Тонкость в том, что Path2D — это абстрактный класс, который не привязан к типу координат — это может быть float или double, для которых есть конкретные реализации Path2D.Float и Path2D.Double. Но ничего не мешало кому-нибудь создать отдельную реализацию абстрактного класса, которая не наследует существующие (обычно это не надо, но мало ли). А теперь в него добавили новый абстрактный метод, в результате чего такая сторонняя реализация перестанет компилироваться при переходе на Java 10. Почему нельзя было в абстрактном классе сделать просто пустой trimToSize, мне неясно. Думаю, качество code review в java.desktop ниже, чем в java.base.


JDK-8182577 Exception when Tab key moves focus to a JCheckbox with a custom ButtonModel


В рамках багфикса одного мутного падения в глубинах логики переключаемых кнопок, в интерфейс ButtonModel вынесли метод getGroup (здесь сделали нормально — у метода есть реализация по умолчанию). В стандартной библиотеке все реализации этого интерфейса наследуют DefaultButtonModel, в которой такой метод уже был. Ну что ж, приятно, что теперь не надо кастовать интерфейс. Забавно, что setGroup уже был в интерфейсе, а вот getGroup не было.


Модуль javax.management


JDK-8044122 MBean access to the PID


Мелкое приятное добавление — метод RuntimeMXBean.getPid() для получения PIDа процесса текущей Java-машины. Раньше люди делали что-то вроде Long.parseLong(mbean.getName().split("@")[0]). Теперь не надо.


JDK-8185003 JMX: Add a version of ThreadMXBean.dumpAllThreads with a maxDepth argument


Если вы мониторите состояние системы, возможно, вам не всегда нужны полные стек-трейсы всех потоков. Теперь у методов ThreadMXBean.dumpAllThreads и ThreadMXBean.getThreadInfo появился перегруженный вариант с дополнительным параметром — максимальной глубиной стектрейса. Это может серьёзно улучшить производительность мониторинга, ведь в энтерпрайз-системах нередко можно увидеть тысячу потоков со стек-трейсами глубиной более сотни фреймов, а чтобы превратить всё это из внутренних записей виртуальной машины в Java-объекты требуется ощутимое время.


Модуль java.net


JDK-8145635 Add TCP_QUICKACK socket option


Появился новый режим работы с сокетами — ExtendedSocketOptions.TCP_QUICKACK. В Си вы уже давно могли им пользоваться для ускорения установления соединения, теперь можно сделать это и в Java без приседаний. Чем плох обычный ACK — объясняет Джон Нейгл (да-да, тот самый).


Заключение


Есть ещё несколько новых методов в модулях jdk.compiler, java.compiler, javafx.controls и jdk.packager.services, но основная масса перечислена выше. Конечно, изменений не так много, как обычно ожидаешь в мажорной версии Java, ну так и версия выходит через полгода после предыдущей. Привыкаем к новому ритму жизни.

Tags:java 10стандартная библиотека
Hubs: Java
+38
19.6k 61
Comments 11