Я уже успел написать учебник по Java 8, а также описать нововведения в Java 9, 10, 11, 12 и 13. Настал черёд, мои друзья, поговорить о том, что нового у нас появилось 17 марта (Месяц Первого зерна) 2020 года с появлением Java 14.
Если вам нужно самое исчерпывающее описание всех новых возможностей, то лучше обратитесь к оригиналу на английском. Здесь же я попробую сконцентрироваться только на нескольких нововведениях, которые мне показались наиболее важными.
Все исходные коды проверялись с помощью JShell, который появился в Java версии 9.
- Pattern matching for instanceof
- Полезные NullPointerException-ы
- Switch Expressions
- Текстовые блоки
- Удалён Concurrent Mark Sweep Garbage Collector
- Records
- JPackage
Pattern matching for instanceof
Это только preview-фича, то есть вполне возможно, что в будущих версиях её уберут, а в текущей 14 версии Java её можно включить с помощью опции --enable-preview:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ ./jshell --enable-preview | Welcome to JShell -- Version 14.0.1 | For an introduction type: /help intro jshell> Object obj1 = java.time.LocalDate.of(2020,4,16); obj1 ==> 2020-04-16 jshell> if (obj1 instanceof java.time.LocalDate myDate) { ...> System.out.println("year=" + myDate.getYear()); ...> } year=2020 jshell> |
То есть внутри instanceof мы уже объявляем переменную myDate нужного типа. Довольно полезно, так как после instanceof почти всегда идёт приведение к типу и присваивание переменной этого типа. До этой фичи это выглядело бы так:
1 2 3 4 5 6 7 |
jshell> if (obj1 instanceof java.time.LocalDate) { ...> java.time.LocalDate myDate2 = (java.time.LocalDate) obj1; ...> System.out.println("year=" + myDate2.getYear()); ...> } year=2020 jshell> |
Я думаю, что такую фичу оставят, довольно разумно выглядит, хотя это и чистый синтаксический сахар. Но это не точно.
Эту возможность можно даже вот так использовать:
1 2 3 4 5 6 |
jshell> if ((obj1 instanceof java.time.LocalDate myDate3) && (myDate3.getYear() > 2000)) { ...> System.out.println("greater 2000"); ...> } greater 2000 jshell> |
Полезные NullPointerException-ы
До Java 14 исключения NullPointerException возвращало мало информации, поэтому находить источник проблемы было довольно сложно.
Например:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
$ ./jshell | Welcome to JShell -- Version 14.0.1 | For an introduction type: /help intro jshell> class A { ...> private B b; ...> public B getB() { return b; } ...> public void setB(B b) {this.b = b;} ...> } | created class A, however, it cannot be referenced until class B is declared jshell> class B { ...> private C c; ...> public C getC() { return c; } ...> public void setC(C c) { this.c = c;} ...> } | created class B, however, it cannot be referenced until class C is declared jshell> class C { ...> private String name; ...> public String getName() { return name; } ...> public void setName(String name) {this.name = name; } ...> } | created class C jshell> A a = new A(); a ==> A@26a1ab54 jshell> B b = new B(); b ==> B@41cf53f9 jshell> C c = new C(); c ==> C@2ef1e4fa jshell> a.setB(b); jshell> String str1 = a.getB().getC().getName(); | Exception java.lang.NullPointerException | at (#8:1) |
Как видим, нам просто указывается NullPointerException с указанием позиции. В Java 14 можно получить более подробную информацию о том, какое именно выражение вернуло непредвиденный null. Однако по умолчанию такие детализированные сообщения отключены. Их можно включить с помощью опции -XX:+ShowCodeDetailsInExceptionMessages, но для этого нам придётся перезапустить JShell, поэтому сначала сохраним выполненные команды в файл, а затем после перезапуска загрузим их:
1 2 3 4 5 6 7 |
$ ./jshell -R-XX:+ShowCodeDetailsInExceptionMessages | Welcome to JShell -- Version 14.0.1 | For an introduction type: /help intro jshell> /open ~/npe.txt | Exception java.lang.NullPointerException: Cannot invoke "REPL.$JShell$13$C.getName()" because the return value of "REPL.$JShell$12$B.getC()" is null | at (#8:1) |
Обратите внимание:
- Мы передали опцию -XX:+ShowCodeDetailsInExceptionMessages через флаг -R, так как нужно его передать именно исполняемой среде, а не самому JShell.
- В сообщении об ошибке мы видим, что REPL.$JShell$12$B.getC() вернуло null, то есть getB() вернуло B, а getC() уже вернуло null.
Switch Expressions
Я уже описывал это изменение про switch expressions, поэтому ещё раз писать не буду, но стоит акцентрировать внимание на том, что в Java 14 они стали частью стандарта.
Имейте ввиду, что yield и var запрещено использовать в качестве имени класса, но они НЕ являются зарезервированными ключевыми словами.
1 2 3 4 5 6 7 8 9 10 |
jshell> int n1 = 100; n1 ==> 100 jshell> String str1 = switch (n1) { ...> case 10 -> {yield "Vasya";} ...> case 100 -> {yield "Petya";} ...> case 110 -> {yield "Monster";} ...> default -> {yield "Default";} ...> } str1 ==> "Petya" |
Текстовые блоки
Так же, как и в прошлый раз, они только как preview. Пример текстового блока:
1 2 3 4 5 6 7 |
String html1 = """ <html> <body> <p>Hello, world</p> </body> </html> """; |
В результате переменная html1 будет содержать следующее:
1 2 3 4 5 |
<html> <body> <p>Hello, world</p> </body> </html> |
Причём в текстовых блоках переводы строк нормализуются в LF (под стиль Unix).
Обратите внимание, что часть начальных пробелов в конечную строку не попала. А именно, не попала та часть пробелов, которая здесь визуализирована в виде точек:
1 2 3 4 5 6 7 |
String html1 = """ ........ <html> ........ <body> ........ <p>Hello, world</p> ........ </body> ........ </html> ........"""; |
То есть был убраны пробелы в каждой строке по количеству пробелов перед завершающим """. Подобное поведение с убранными пробелами позволяет вплетать текстовые блоки в окружающий код, не ломая общую структуру.
Символы кавычек в текстовых блоках можно использовать без ограничений, но если мы хотим использовать три машинописные кавычки подряд, то их нужно предварять одной косой чертой:
1 2 3 4 5 6 7 |
String text1 = """ Мы можем использовать "кавычки без ограничений" String text = \""" Бла-бла-бла \"""; """; |
В результате text1 будет содержать следующее:
1 2 3 4 5 |
Мы можем использовать "кавычки без ограничений" String text = """ Бла-бла-бла """; |
Все \r, \n, \b и аналогичные, которые работали в строках, в текстовых блоках работают.
Также появились новые специальные последовательности. Например, \s переводится в обычный пробел, что можно использовать для выравнивания пробелами строк до определенной длины:
1 2 3 4 5 |
String colors = """ красный \s синий \s зеленый \s """; |
Если нам нужно создать длинную строковую константу, то мы можем использовать символ \ в конце каждой строки, чтобы отменить перевод на новую строку в результирующем значении.
Пример:
1 2 3 4 5 |
String text = """ Lorem ipsum dolor sit amet, consectetur adipiscing \ elit, sed do eiusmod tempor incididunt ut labore \ et dolore magna aliqua.\ """; |
Приведённый выше код аналогичен следующему:
1 2 3 |
String literal = "Lorem ipsum dolor sit amet, consectetur adipiscing " + "elit, sed do eiusmod tempor incididunt ut labore " + "et dolore magna aliqua."; |
Имейте в виду, что текстовые блоки всё ещё только в качестве preview, чтобы ими воспользоваться, нужно использовать флаг --enable-preview.
Удалён Concurrent Mark Sweep Garbage Collector
Сборщик мусора CMS (Concurrent Mark Sweep) удалён в Java 14. Светлая память ему. Но я, честно говоря, даже не помню про него ничего.
Records (записи)
Опять же только в качестве preview.
Записи предполагается использовать для облегчения создания классов, которые имеют состояние, но не имеют поведения. Пример:
1 |
record Point(double x, double y) {} |
Записи автоматически получают приватные переменные для хранения своего состояния, методы hashCode, equals, toString, работающие с переменными состояния, а также методы для получения значения каждой из переменной состояния.
Сами записи неявно final, их переменные состояния тоже final.
1 2 3 4 5 6 7 8 9 10 11 12 |
$ ./jshell --enable-preview | Welcome to JShell -- Version 14.0.1 | For an introduction type: /help intro jshell> record Point(double x, double y) {} | created record Point jshell> Point point = new Point(100.2, 200.4); point ==> Point[x=100.2, y=200.4] jshell> point.x() $3 ==> 100.2 |
JPackage
Пока только как incubator, что, по всей видимости, означает, что он может быть удалён в последующих релизах.
Пример использования:
1 |
$ jpackage --name myapp --input lib --main-jar main.jar --type pkg |
Упакует всё в формат pkg из каталога lib, а основной класс для запуска в lib/main.jar и описан в его MANIFEST.MF.