
В эту среду, 30 августа, в офисе компании Oracle состоялась встреча JUG.ru с Олегом Шелаевым, Developer Advocate в компании ZeroTurnaround, которая делает JRebel и XRebel. Тема встречи — инструменты создания многопоточных программ на Java (от конструирования велосипедов и запуска потоков ручками, до ForkJoinPool-ов, зеленых потоков и транзакционной памяти).
Конечно, мы поинтересовались, какие фишки Java 9 считают в ZeroTurnaround наиболее полезными. В результате, разжились статьей, которую вы сейчас читаете. Оригинал статьи Олег опубликовал в блоге RebelLabs, там есть еще много интересного.
Итак, начали.
Мы долго ждали Java 9, и вот, релиз уже не за горами. Ура! Это был не самый простой путь, но тем не менее, даже самые жаркие споры о системе модулей потихоньку продвигаются вперед, и большинство участников приходят к согласию. Скорей всего, релиз состоится совсем скоро, например, 21 сентября 2017 года.
В этом посте я не буду останавливаться на детальном обсуждении системы модулей. Вместо этого, мы поговорим о вещах, которые сможет использовать любой разработчик: о нововведениях в языке Java и стандартных API.
Дальше будет список наших любимых новшеств в Java 9 API. В принципе, чтобы понять суть, достаточно взглянуть на примеры кода. Но можно запустить JShell и самостоятельно все эти примеры, своими глазами взглянуть на результаты.
Запускай JShell, я подожду… готов? Нет еще? Окей… сделал? Все еще нет? Да, на разгорев нужно время… ок, запустилось, отлично! Начинаем.
Фабричные методы для коллекций
Одна из наиболее ожидаемых фишек Java 9 — возможность делать литералы коллекций, чтобы удобней записывать самые простые случаи. Шутка, конечно, мы же говорим о Java, литералов у нас нет, есть только статические фабричные методы. Тем не менее, теперь мы можем легко создавать List, Map и Set, используя готовые методы:
jshell> List.of(1, 2, 3)
$1 ==> [1, 2, 3]
jshell> Set.of(1, 2)
$2 ==> [2, 1]
jshell> Map.of("hello", "world")
$3 ==> {hello=world}Это стало возможным благодаря появлению статических методов в интерфейсах (Java 8), использовать которые научились List, Map и Set.
В результате, создается немутабельная коллекция, оптимизированная для работы с максимальной производительностью. Например, List1 хранит значение в поле класса, что ускоряет доступ до него.
jshell> List.of(1).getClass()
$4 ==> class java.util.ImmutableCollections$List1Стримы
В Stream API добавили парочку очень полезных возможностей. В частности, методы dropWhile и takeWhile. Как можно предположить по названию, dropWhile выбрасывает элементы с начала и до тех пор, пока не будет выполнено условие, а takeWhile — забирает элементы вплоть до выполнения условия.
jshell> IntStream.range(1, 10).dropWhile(x -> x < 5).forEach(System.out::println)
5
6
7
8
9Следующее полезное дополнение — метод iterate(). Он позволяет заменять циклы стримами. Нужно передать ему изначальные значение стрима, условие (когда нужно остановить итерации), и функцию перехода (как именно будет получаться следующий элемент).
jshell> IntStream.iterate(0, x -> x < 3, x -> x + 1).forEach(System.out::println)
0
1
2Если вам вдруг хотелось делать вычисления с фиксированной точкой на стримах, эта мечта может исполниться в Java 9.
Optional
Если вдруг кто не помнит, как их использовать, у нас есть отличная шпаргалка. В Java 9 наконец-то добавили метод or(), позволяющий в одну строчку связывать разные Optional’ы, не опускаясь до постоянных проверок на isPresent().
public Optional<T> or(Supplier<? extends Optional<? extends T>> supplier)
jshell> Optional.empty().or(() -> Optional.of("RebelLabs"))
$5 ==> Optional[RebelLabs]Следующее хорошее дополнение — возможность преобразовывать Optional в стрим, содержащий не более одного элемента. Это реально полезно, если хочется использовать ленивые стримы. В примере ниже можно увидеть разницу своими глазами. Если вызвать map() на Optional, маппинг произойдет мгновенно, а вот на стриме — нет.
jshell> Optional.of(1).map(x -> x * 3)
$10 ==> Optional[3]
jshell> Optional.of(1).stream().map(x -> x * 3)
$11 ==> java.util.stream.ReferencePipeline$3@67b92f0aИ наконец, у нас появился метод ifPresentOrElse. В Java 8, можно было определить поведение только для случая, когда значение Optional существует. В Java 9 стало возможно указать два разных Runnable, определяющих что делать, если значение существует, и если не существует.
jshell> Optional.empty().ifPresentOrElse(x -> System.out.println(x), () -> System.out.println("empty"));
emptyCompletable future
Еще одним кусочком API, который хорошенько заполировали, оказался класс CompletableFuture. В него добавили парочку отличных вещей, позволяющих писать еще более корректный многопоточный код.
Один из самых крутых методов — это copy(), который возвращает немутабельную копию этого CompletableFuture. В следующем примере, мы создаем CompletableFuture, делаем его копию и проверяем, что завершение копии не влияет на исходный объект. Это очень-очень полезно, когда создается асинхронный API, возвращающий CompletableFuture. Раньше нужно было знатно помучиться, обруливая ситуации, когда клиент может сам завершить CompletableFuture, возвращенное из такого API. Сейчас достаточно просто взывать метод copy().
jshell> CompletableFuture<String> future = new CompletableFuture<>()
future ==> java.util.concurrent.CompletableFuture@35d176f7[Not completed]
jshell> future.copy()
$15 ==> java.util.concurrent.CompletableFuture@4973813a[Not completed]
jshell> future.isDone()
$17 ==> false
jshell> $15.isDone()
$18 ==> false
jshell> $15.complete("JRebel")
$19 ==> true
jshell> $15.isDone()
$20 ==> true
jshell> future.isDone()
$21 ==> falseНо самое крутое в том, что остановка родителя распространяется на все копии!
jshell> CompletableFuture<String> future = new CompletableFuture<>()
future ==> java.util.concurrent.CompletableFuture@4bbfb90a[Not completed]
jshell> future.copy()
$24 ==> java.util.concurrent.CompletableFuture@5a8806ef[Not completed]
jshell> future.complete("XRebel")
$25 ==> true
jshell> $24.isDone()
$26 ==> trueКроме того, наконец-то в них добавили таймауты. Работа с асинхронным API, без наличия встроенных функций работы с таймаутами, была весьма напряжной. В Java 9 стало возможным точно определять способ завершения CompletableFuture, после истечения вручную заданного промежутка времени.
jshell> CompletableFuture<String> future = new CompletableFuture<>()
future ==> java.util.concurrent.CompletableFuture@67205a84[Not completed]
jshell> future.completeOnTimeout("Isn't this amazing", 1, TimeUnit.SECONDS)
$28 ==> java.util.concurrent.CompletableFuture@67205a84[Not completed, 1 dependents]
jshell> future.isDone()
$29 ==> trueAPI управления процессами
До Java 9, управление процессами было совершено не таким кроссплатформенным, как хотелось бы верить. Работа с подпроцессами раньше была несколько кривой, и вот в Java 9 её наконец выпрямили. Java 9 добавляет класс ProcessHandle, который предоставляет API для анализа текущего процесса, других процессов, найденных по пиду, их дочерних процессов, и так далее. Просто посмотрите на пример:
jshell> ProcessHandle current = ProcessHandle.current();
current ==> 6349
jshell> current.pid()
$33 ==> 6349
jshell> current.info().\TAB
arguments() command() commandLine() equals( getClass()
hashCode() notify() notifyAll() startInstant() toString()
totalCpuDuration() user() wait(
jshell> current.info().command()
$34 ==> Optional[/Library/Java/JavaVirtualMachines/jdk-9.jdk/Contents/Home/bin/java]Скорей всего, самой часто используемой функцией будет получение командной строки и аргументов, использованных для запуска процесса, но остальные фичи тоже вполне достойны внимания.
Еще одна популярная задача, которую будет куда удобней делать в Java 9 — запуск кода сразу после того, как процесс завершился. Java 9 предлагает для этого воспользоваться новым методом:
CompletableFuture<Process> onExit()Указываете, что же вы хотите сделать, и это просто работает. Нет больше слёзок и нестабильных чужих библиотек.
StackWalker
Возрадуйтесь, ненавистники исключений! Теперь можно работать со стектрейсами, не создавая объектов Exception. Добро пожаловать в StackWalker!
StackWalker дает возможность бродить по стеку, фильтровать его, и эффективно делать разные другие вещи. Этот пример выдернет верхние 5 элементов из стектрейса:
jshell> StackWalker.getInstance().walk(s -> s.limit(5).collect(Collectors.toList()));
$36 ==> [do_it$(java:36), jdk.jshell/jdk.jshell.execution.DirectExecutionControl.invoke(DirectExecutionControl.java:209), jdk.jshell/jdk.jshell.execution.RemoteExecutionControl.invoke(RemoteExecutionControl.java:116), jdk.jshell/jdk.jshell.execution.DirectExecutionControl.invoke(DirectExecutionControl.java:119), jdk.jshell/jdk.jshell.execution.ExecutionControlForwarder.processCommand(ExecutionControlForwarder.java:134)]Улучшения в языке Java
Улучшаются не только API, но и сам язык. Во-первых, символ _(подчёркивание) больше не является корректным идентификатором. Если вы зачем-то его используете, придется перейти на двойное подчеркивание! (Подсказка: не делайте этого).
jshell> int _ = 1
| Error:
| as of release 9, '_' is a keyword, and may not be used as an identifier
| int _ = 1
| ^
| Error:
| reached end of file while parsing
| int _ = 1
| ^
jshell> int __ = 1;
__ ==> 1Это для того, чтобы в будущем можно было заменять на подчеркивание ненужные (опциональные) параметры при вызове функции.
Интерфейсы тоже немного доработали. Интерфейсы в Java 9 смогут содержать приватные методы. В Java 8 мы получили возможность хранить некую общую логику в default-методах. Теперь мы сможем выделять общую логику и внутри интерфейсов, без необходимости создания вспомогательных классов.
Вот вам небольшой синтетический пример:
jshell> interface A { private int zero() { return 0;} default int one() { return zero() + 1;}}
| created interface AНу и наконец, последнее новшество. Теперь можно использовать effectively final переменные в блоках try-with-resources. Это упрощает код, не нужно больше объявлять переменные внутри try. Просто работаете с ними в блоке try, и это компилируется.
boolean a() throws Exception {
Socket s = new Socket();
try (s) { }
return s.isClosed();
}После выполнения блока try, все упомянутые там AutoClosable закономерно закроются.
Заключение
Ура! Мы рассмотрели кучу всего, и это далеко не все новшества, появившиеся в Java 9. Тем не менее, перечисленные выше вещи кажутся нам наиболее полезными, и пользоваться ими будут при первой возможности.
Выпуск Java 9 готовится уже довольно давно, и всем нам пора хорошенько разобраться, как она на нас повлияет. Сейчас это уместно как никогда, учитывая, что режим работы с clasthpath останется неизменным, и переход на Java 9 может оказаться простым и безболезненным. Можно прямо сейчас скачать готовую сборку Java 9, разобраться с новыми API, распробовать их, и приготовиться к наступающему светлому будущему!
Авторы
Олег Шелаев — Java-разработчик и Developer Advocate в ZeroTurnaround. Когда не занимается написанием java agent-ов или тестов, пишет в блог RebelLabs или выступает на конференциях. В свободное время пытается продвигать науку в Тартуском университете, изучая проблемы динамических обновлений программ.
Комментарии (77)

Tyrr
31.08.2017 16:22+3Хорошая статья! на некоторых моментах прям выдохнул) но выбор между интерфейсом и абстрактным классом теперь кажется сложнее, есть ли однозначный ответ, когда что выбирать?

ferasinka
31.08.2017 16:27+1У интерфейса нет и не может быть состояния.

olegchir Автор
31.08.2017 16:33+7public interface Отрывать { AtomicInteger руки = new AtomicInteger(10); private static void заТакое() { руки.set(11); } }
Beholder
31.08.2017 17:25+2Ну и это поле будет public static final, то есть состояния у интерфейса всё равно нет.

igorperciuleac
31.08.2017 18:29оно ведь mutable

Beholder
31.08.2017 18:32+3Ну состояние будет одно на весь classloader, а не на каждый объект, который реализует этот интерфейс.

gamerforEA
01.09.2017 18:07+4public interface StateInterface { Map<StateInterface, Integer> states = new IdentityHashMap<>(); default int getState() { return states.getOrDefault(this, 0); } default void setState(int state) { states.put(this, state); } }

AstarothAst
31.08.2017 17:02+1CompletableFuture<Process> onExit()
Я правильно понимаю, что с помощью этой штуки можно сделать процесс-маклауд? :)
izzholtik
31.08.2017 17:33Множественное наследование всё ближе? >:D
А если серьёзно, то какого чёрта класс интерфейса содержит метод, возвращающий какую-то «избранную» реализацию?
— не туда написал. Интерфейс у хабра всё ещё не тотр.
olegchir Автор
04.09.2017 13:00+1в таких случаях принято комментарий целиком заменять на слово «del», и потом писать в правильное место. Их обычно не минусуют, потому что всем и так ясно, откуда пошла такая фигня

olegchir Автор
31.08.2017 17:45+5Борис Бритва, или Борис-Хрен-Попадешь, резкий, как удар серпом по яйцам, и жесткий, как удар молотом. Живой советский герб. Говорят, эту сволочь вообще невозможно убить.
www.youtube.com/watch?v=n7itx7O3b_k

Beholder
31.08.2017 17:33-2Половина или больше этих фич уже есть в Kotlin.
Вот почему-то ничего не пишут, закрыли ли они кучу багов и реквестов по JavaFx.Optik
01.09.2017 07:11+7При чем здесь вообще котлин? Все его фичи тоже уже были в других языках.

werewolfspb
01.09.2017 14:00-3При том, что, выходит, изменения в языке / stdlib уже почти не нужны при 100% Java <-> Kotlin interop — если кому нужен сахар, но возьмёт Котлин и будет использовать его. Или любой другой JVM-based язык и компилятор.
Можно было бы сосредоточиться на платформе и hardcore фичах, типа modularity, а не на языке. Тогда бы и апдейты, глядишь, быстрее выходили.
Optik
01.09.2017 14:44+1В одном из предыдущих
срачейтопиков путем докапывания до котлин-адепта выяснилось, что интероп все же совсем не 100%. Также как и у других jvm языков.
Тем кто пишет core-либы смириться и жить на старых возможностях языка? Стоит забить на тех, кому нравится java?
werewolfspb
01.09.2017 15:15-1Пришлите ссылку на топик, пожалуйста, если не лень, я бы с удовольствием почитал. Есть, конечно, нюансы, но они достаточно в крайних случаях мешают жить.
Нет, на тех, кому нравится Java, не забить, претензии только к срокам выхода обновлений. Так-то это всё полезные безусловно вещи. Надеюсь, Гёц сотоварищи таки будут продвигать апдейты пошустрее.
Просто те, кому нравится ТОЛЬКО Java, должны понимать, что они вот с такой скоростью будут получать обновления и плюшки. Это, кроме всего прочего, означает, что молодёжь будет изучать другие языки тоже и с бОльшим удовольствием придёт туда, где пишут не только на чистой Java.
dzugaru
02.09.2017 00:29+2>Это, кроме всего прочего, означает, что молодёжь будет изучать другие языки тоже и с бОльшим удовольствием придёт туда, где пишут не только на чистой Java.
Слава богу, хипстеров нам в джаве только еще и не хватало. Пусть на джаваскрипте пишут.
guai
01.09.2017 14:44+3100% Java <-> Kotlin
cool story, bro
werewolfspb
01.09.2017 15:16-2:) Киньте ссылок на доказательства обратного, пожалуйста, если есть :)
guai
01.09.2017 17:53ссылок нет. опыт есть. рекурсивные дженерики с котлином не пробовали поженить?
да и не рекурсивные, бывает, глючат. Comparable с дженериком ему чота не нравится.
«Добавьте мне override». Добавил. «Я не понимаю, зачем тут override» :)
Приходилось делать странные финты ушами, типа сделать интерфейс на котлине, от него занаследовать явавский класс, от него опять котлин, чтоб только удовлетворить компилятор.
werewolfspb
01.09.2017 18:09Понятно, спасибо. Пока не пробовал с generics, попробую. Это какая версия была у вас?
vlanko
01.09.2017 15:44+2фигню говорите.
Оракл хотел ввести модульность. Но им не далиговнокодеры, не желающие переписывать свои программы. //Red Hat и IBM
olegchir Автор
04.09.2017 12:57Имхо там слегка сложней.
Есть договоренность, что если спека всеми принята, то ее делают.
А тут получилось, что люди прямо перед выпуском напрямую отказались от своих слов, посчитав их глупостью.
И дело там не только в «переписывать программы», а в том, что изначальную спеку нужно делать куда масштабней и умней.
Обычная мелкая гражданская война, в которой все правы и неправы одновременно, а страдают вообще другие люди :)

DigitalSmile
01.09.2017 09:26Вот почему-то ничего не пишут, закрыли ли они кучу багов и реквестов по JavaFx
По состоянию на 2016 год (если не читали):
static.rainfocus.com/oracle/oow16/sess/1462485593256001c2xn/ppt/JavaFX%209%20-%20New%20and%20Noteworthy.pdf

olegchir Автор
01.09.2017 19:08+2Java нужна потому, что это Java. Язык с невероятно долгой совместимостью и неформальными гарантиями что новые фичи будут хорошо работать очень долго (может быть, те же фичи будут использовать наши внуки), с хорошо подогнанными друг другу фичами (проходят годы обсуждений, прежде чем какая-то фича попадает на публику), с поддержкой Oracle, ну и в конце концов — это язык, на котором говорит большинство разработчиков в мире. Котлин — хороший язык, но он не об этом. Тем, кому нужна Java, нужна именно Java.

Crandel
31.08.2017 17:59-> IntStream.range(1, 10).dropWhile(x -> x < 5).forEach(System.out::println) | Error: | cannot find symbol | symbol: variable IntStream | IntStream.range(1, 10).dropWhile(x -> x < 5).forEach(System.out::println) | ^-------^
Ubuntu 16.04
jshell 9-internal java -version java version "9" Java(TM) SE Runtime Environment (build 9+181) Java HotSpot(TM) 64-Bit Server VM (build 9+181, mixed mode)
k0ldbl00d
31.08.2017 18:12-1JRE станет еще более параноидальным?

Lure_of_Chaos
01.09.2017 09:02+1Если Вы о системе модулей, то ДА.
k0ldbl00d
01.09.2017 10:23-1Я о том, что я не Java-программист, а простой админ, и у меня есть горстка не нового, но вполне работоспособного железа, которое управляется через Java-апплеты, и с каждым мажорным релизом их всё сложнее запустить, потому что обновлений к этим железкам уже никто не выпустит, а параноики из Oracle не дают возможности отключить проверки, которые мешают запускать апплеты, и вообще мне не нужны.

Lure_of_Chaos
01.09.2017 10:35+2Врядли апплеты можно закопать еще глубже, чем их уже закопали, так что нововведения уже совсем не о том.
(хотя я лично скорблю — хороня флеш уже сколько баянов порвали, а такие вкусные апплеты, имхо с меньшими проблемами безопасности, убили прямо в расцвете их сил)
Но также я наблюдаю развитие языков программирования как платформ. Java, увы, полностью исчезла из интернета, ради которого создавалась, и ушла в глухой сервер-энтерпрайз и мобайл(андроид).

snuk182
31.08.2017 18:31+4jshell> Map.of("hello", "world") $3 ==> {hello=world}
Полна боли и страдания жизнь без кортежей и/или классов данных.
olegchir Автор
31.08.2017 18:52+3Да, поэтому:
1) в конпеляторе есть исследование литералов: http://openjdk.java.net/jeps/186 (это именно исследование, а не финальная спека). Она прям напрямую связана, с обсуждаемой здесь фичей convenience methods (http://openjdk.java.net/jeps/269). Фичу курирует Гёц.
2) уже есть проротипы type values, я писал о них недавно, и буду еще. Фичу курирует Гёц же, и Роуз.
Короче, всё будет, но не мгновенно. Возможно, ускорение релизного цикла, о котором недавно писал Рейнхольт, позволит выкатить подобные mast-have фичи поперед других, более масштабных, изменений, и мы увидим их в ближайшем будущем.
Nakosika
31.08.2017 18:57Почему они просто в библиотеку не добавили методы Optional? Зачем ждать новой версии языка?

cypok
01.09.2017 06:56+1Это не новая версия языка, это новая версия платформы Java, куда входит и язык, и стандартная библиотека, и тулинг (
jshell, например).

AndreyRubankov
05.09.2017 09:57К счастью, в Java мире нету апгрейдов стандартной библиотеке в отрыве от выпуска новой версии платформы.
Любое изменение SDK должно быть только в мажорных релизах, иначе будет хаос и про существование стабильности можно будет забыть.
И да, ускорение релизного цикла – это было бы очень круто!
Но с планами по развитию платформы, ускорение релизного цикла, это не такой простой шаг. Зарелизить те же Панаму или Валхаллу – это не так просто будет и скорее всего повлечет откладывания релизов на полгода-год.

AlexTheLost
01.09.2017 00:02+7Выглядит и звучит так же, как когда чиновники говорят о прорывах которые в других странах появились уже 10 лет назад.)
Что например мешало добавить удобные методы для создания и манипуляции коллекциями раньше, мне не понятно.

Lure_of_Chaos
01.09.2017 08:52+5приватные методы в интерфейсах
Ребята, пора уже честно признаться — вам очень не хваталомножественного наследованияпримесей, но вы стесняетесь вводить это бгмерзкое понятие и поэтому распидколбашиваете идеологию интерфейсов.
Интерфейсов, Карл! (запах горелого)

Lure_of_Chaos
01.09.2017 08:58кстати, поправьте меня, если я неправ, но запилить некоторые примочки Optional и .of() можно собственными руками уже в java8

fzn7
01.09.2017 09:10Раз пошла такая пьянка, предлагаю выпилить for

Lure_of_Chaos
01.09.2017 10:15Выпиливать — не наш метод.
А вот без Properties, как в C#, до сих пор грустно. Как хорошо, что есть Lombok, правда, IDE его все так же не понимают…
olegchir Автор
01.09.2017 11:19Гёц когда-то об этом говорил. Проблема в том, что проперти можно реализовать по-разному, и никакая из реализаций не подойдет всем желающим (включая желающих совместимости для легаси двадцатилетней давности). А маркетинговый эффект этой фичи будет минимальный, по сравнению с приложенными усилиями (т.е. если запилить что-то важное типа лямбд — это скорей будет стимулом поменять $languagename на Java, а вот введение пропертей — не особо)
Nakosika
01.09.2017 13:37+2Зачем они? Только баги провоцируют. Читаешь код и хз что происходит пока все поля не прощелкаешь и не убедишься что это не функции на самом деле.

blaketsk
01.09.2017 12:59Это стало возможным благодаря появлению статических методов в интерфейсах (Java 8)
Дефолтных?
olegchir Автор
01.09.2017 13:00public interface HelloWordable { public static void hello() { System.out.println("Hello World!"); } }
hazard2
01.09.2017 15:34+2С удивлением понял, что можно написать вот так:
public interface Main { public static void main(String[] args) { System.out.println("Hello World!"); } }
Или даже вот так:
@SpringBootApplication @Configuration public interface TestApplication { public static void main(String[] args) { ApplicationContext ctx = SpringApplication.run(TestApplication.class, args); } }

elegorod
04.09.2017 12:27List.of(1, 2, 3)
Неожиданно, что это возвращает неизменяемый List. Типичная ситуация в моём коде — создать List или Map с парой элементов, а потом добавить туда ещё несколько. Через List.of это сфейлится в рантайме с Exception-ом. Думаю, многие проограммисты на этом обожгутся.
Как теперь лучше создавать изменяемые списки, вот так?
new ArrayList(List.of(1, 2, 3))
Лучше бы сделали ArrayList.of(1, 2, 3)
olegchir Автор
04.09.2017 12:47+1Вопрос в том, что стратегический план должен корректировать не только саму форму API, но и способ того, как люди пользуются Java. Направлять мир в правильное русло. Это один из смыслов существования архитектурных советов и выпуска ими рекомендаций. Если бы нужен был просто вспомогательный метод, сделать его не проблема, или уже стащить готовый из Гуавы.
С точки зрения всеобщей схемы всего, верной практикой считается использование иммутабельных конструкций. Нужно всячески поощрять разработчиков к иммутабельности, а стиль программирования — к функциональной чистоте.
Если бы существовала мутабельная версия этой конструкции, все говнокодеры мира тут же начали бы ее использовать (не нужно учиться новым вещам, плодить мутабельное всегда проще), а всем остальным пришлось бы смириться с таким положением вещей по причине частоты распространения.
Имхо, именно поэтому, для создания мутабельной версии выбрана специально переусложненная форма. Именно такая как у тебя в примере — использование копирующего конструктора поверх коллекции, полученной через convenience method.
Я не чтобы оракул оракла, это чуть ли не открытым текстом сказано в JEP-269 Convenience Factories.
В дальнейшем эти идеи могут быть развиты с помощью JEP-186 Collection Literals. Предлагается не мусорить с помощью List.of, а сразу писать List list = #[ 1, 2, 3 ];. Но это совершенно отдельный (тоже стратегический) вопрос.
olegchir Автор
Друзья, внесу немного оффтопика сейчас, т.к. завтра будет поздно. Сегодня (31 августа) — последний день, когда на Joker 2017 всё еще можно купить билеты по вкусной летней стоимости. До конца дня остались считаные часы. Кто хотел определиться — определяйтесь сейчас.