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

    Недавно состоялся релиз-кандидат 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, ну так и версия выходит через полгода после предыдущей. Привыкаем к новому ритму жизни.

    Поделиться публикацией
    Комментарии 11
      +2
      Спасибо! Интересная и полезная информация.
        0
        Ещё одна доработка большой фичи, появившейся в Java 9: Multi-Release JARs. Напомню, это возможность в одном JAR-файле иметь несколько версий одного и того же класса или ресурса, соответствующих разной версии Java.

        Мне одному кажется, что это заведомо рудиментарная вещь? Она идет вразрез с имеющимися билд тулзами, процессом сборки и выкатывания, и вообще со здравым смыслом. Версионирование должно быть на уровне всей библиотеки, как это делается сейчас, а не отдельных классов. А уже рантайм должен (был) уметь цеплять версию библиотеки, соответствующей релизу, если такая присутствует.


        Общее впечатление: я так понимаю, эволюция Java Api остановилась, и будущие изменения будут затрагивать по большей части JVM?

          +2
          Мне одному кажется, что это заведомо рудиментарная вещь?

          Пока неясно, но мне видится польза в том, что у вас один и тот же JAR-файл с разной версией JDK может работать с разной степенью эффективности без приседаний с рефлекшном. Представьте, что в новой версии JDK появился метод, который сделает код вашей библиотеки более быстрым. Однако напрямую использовать вы его не можете, потому что обещаете совместимость с предыдущей версией JDK. Можно собирать два джарика для разных версий JDK, но это очень неудобно и неэффективно. Пользователь не сможет автоматом получить преимущества, просто задеплоив приложение на сервер с более новой JDK: ему придётся иметь опциональные зависимости в проекте и знать при сборке, какая версия JDK будет на целевом сервере. Да и автору библиотеки неудобно поставлять несколько сборок. Если таких отличий немного, обычно втыкают грязный рефлекшн, а не плодят несколько версий библиотеки. С Multi-Release JARs можно обойтись без рефлекшна. Хотя неизвестно что лучше, потому что сборка проекта усложняется и поддержка в IDE далека от идеала. Время покажет.


          Общее впечатление: я так понимаю, эволюция Java Api остановилась, и будущие изменения будут затрагивать по большей части JVM?

          Вряд ли. Java 10 — это первый полугодовой релиз. До этого все колбасили большие фичи, стараясь изо всех сил их впихнуть в девятку, которую делали три с лишним года. А в эти полгода задела больших фич, которые можно пускать в продакшн, не осталось, поэтому релиз по большей части проходной. Но это не значит, что над большими фичами не работают, просто они будут позже.

            +1

            Все зависит от глубины изменений. Если речь идет только о новом методе или классе, то самый безболезненный способ — это сделать обычный интерфейс-враппер, а имплементацию подключать динамически через factory или singleton. Уверен, что когда количество изменений вырастет, появятся всякие backport-библиотеки вроде retrolambda. Теперь представьте, что поменялась версия байткода. Тогда по-любому придется делать два билда с разными --target и собирать два разных джарника. Либо если произошла радикальная смена API как в случае с Java8, то вообще придется говорить о двух разных версиях библиотеки, собранной из разных исходников. Multirelease jars же все усложняют: лейаут проекта, компиляцию, сборку и тестирование. Вобщем, поживем-увидим. Пока что пипл не слишком с энтузиазмом относится к фиче.

          0
          Ого, а как ты за этим следишь? По какому принципу смотришь баги и читаешь рассылки? Наверное, не всё подряд, иначе это заняло бы слишком много времени…
            +2

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

            0
            А где можно популярно почитать про «обновлённый стандарт Unicode и строку locale». Не спецификацию, а мотивацию — зачем вообще в Unicode нужем часовой пояс?!!! И как Java locale связана с Unicode locale? Из-за того, что форматер переводит дату в строчку, которая всегда Unicode?
              0
              Как я понимаю, это не сам Unicode стандарт символов, а некий дополнительный проект консорциума Unicode — [CLDR](http://cldr.unicode.org/index), который в целом направлен на стандартизацию интернационализации приложений. Мотивация понятна — удобно, когда можно в одной строчке закодировать все настройки, с которыми необходимо вывести данное сообщение в интерфейсе конкретному пользователю.
                +2

                Честно говоря, чтение это сайта породило еще больше вопросов. Вот это порадовало — "it may want to also remember the user's time zone, ..., smoker/non-smoker preference, meal preference (vegetarian, kosher, and so on), music preference, religion, party affiliation, favorite charity, and so on". Запасаюсь попкорном в ожидании поддержки форматирования в Jave в зависимости от сорта канабиса :)

              0
              Вот смотрю я на такой код и как-то не впечатлен. Применение instanceof всегда было для меня признаком плохого дизайна.

              static <E> List<E> copyOf(Collection<? extends E> coll) {
                      if (coll instanceof ImmutableCollections.AbstractImmutableList) {
                          return (List<E>)coll;
                      } else {
                          return (List<E>)List.of(coll.toArray());
                      }
                  }
                +1

                Вы, наверно, давно учились. Это же обычный паттерн-матчинг, просто в Java пока нет синтаксиса для него. Оптимизация частного случая конкретной реализации без изменения семантики — прекрасное применение instanceof. Альтернатива — паттерн visitor, который сегодня уже кажется бойлерплейтом.

              Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

              Самое читаемое