Версия Java 17 была выпущена не так уж давно. Отличие этого релиза в том, что это — новая TLS-версия (Long Term Support, с долговременной поддержкой) после Java 11.
В этой статье рассмотрим новые практические функции, которые были введены между 11-ой и 17-ой версиями.
- Switch-выражения.
- Текстовые блоки.
- Сопоставление с образцом (Pattern Matching) для instanceof.
- Полезные NullPointerException.
- Записи (Records).
- Запечатанные (sealed) классы.
- Сопоставление с образцом для switch.
Switch-выражения
Switch-выражения — это оператор switch с улучшенным синтаксисом и функциональностью. Как они работают?
Выведем информацию о том, является ли данный день будним или выходным. При использовании операторов switch это выглядит так:
DayOfWeek dayOfWeek=// назначение значений switch (dayOfWeek) { case SUNDAY: case SATURDAY: System.out.println("Weekend"); break; case FRIDAY: case THURSDAY: case WEDNESDAY: case TUESDAY: case MONDAY: System.out.println("Weekday"); break; default: System.out.println("Unknown Day!"); }
С помощью новых switch-выражений этот код можно переписать так:
System.out.println(switch (day) { case SUNDAY, SATURDAY -> "Weekend"; case FRIDAY, THURSDAY, WEDNESDAY, TUESDAY, MONDAY -> "Weekday"; });
В чем разница?
Во-первых, теперь можно определять более одного условия для одного и тот же случая.
Во-вторых, больше не нужно использовать ключевое слово break, чтобы остановить выполнение. При использовании switch-выражений выполняется только правая часть соответствующего случая, если применяется синтаксис со стрелкой (->).
В-третьих, поскольку операторы switch стали switch-выражениями, а выражения вычисляют значение, то теперь они могут возвращать значение.
В-четвертых, выражения switch являются исчерпывающими. Если вы забудете указать случай в выражении switch, то получите ошибку во время компиляции. Если вы охватываете все случаи, вам не нужно иметь случай “по умолчанию”.
В-пятых, если необходимо выполнить блок кода, то поддерживается следующее:
System.out.println(switch (day) { case SUNDAY, SATURDAY -> "Weekend"; case FRIDAY, THURSDAY, WEDNESDAY, TUESDAY, MONDAY -> { // какая-то логика yield "Weekday"; } });
Просто откройте блок, выполните необходимые действия и верните значение, воспользовавшись ключевым словом yield.
Текстовые блоки
Текстовые блоки — это просто многострочные строковые литералы.
Чтобы поместить в код такой JSON:
{ "name":"fatih", "surname":"iver", "birthYear":1996 }
Нужно отформатировать его таким образом:
String json="{n" + " "name":"fatih",n" + " "surname":"iver",n" + " "birthYear":1996n" + "}";
С помощью текстовых блоков можно просто написать следующее:
String json=""" { "name":"fatih", "surname":"iver", "birthYear":1996 } """;
С текстовыми блоками больше не нужно экранировать кавычки или объединять несколько линий, чтобы сформировать одну строку.
Особенно это облегчает жизнь, если вы храните HTML, JSON, SQL и XML в исходном коде.
Новые текстовые блоки делают код более красивым и простым для чтения.
Сопоставление с образцом (Pattern Matching) для instanceof
instanceof позволяет проверить, принадлежит ли объект к нужному типу. Если это так, мы приводим этот объект к желаемому типу, чтобы вызвать метод, доступный только для этого типа. Пример:
Object o="Hello, World!"; if (o instanceof String) { String s=(String) o; System.out.println(s.length()); }
При сопоставлении с образцом не нужно явное приведение типов:
Object o="Hello, World!"; if (o instanceof String s) { System.out.println(s.length()); }
Вы даже можете использовать переменную s, как это показано ниже, если выражение instanceof принимает значение true:
Object o="Hello, World!"; if (o instanceof String s && !s.isBlank()) { System.out.println(s.toLowerCase()); }
Полезные NullPointerException
При выполнении следующего кода:
Map<String, String> map=new HashMap<>(); System.out.println(map.get("key").toLowerCase());
Мы получим:
Exception in thread "main" java.lang.NullPointerException at Main.example(Main.java:14) at Main.main(Main.java:10)
Фрагмент лишь сообщает, в какой строке произошло исключение. При этом ничего не сказано о том, приняла ли карта значение null или же в ней не оказалось нужного ключа.
С новым улучшением мы получаем:
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.toLowerCase()" because the return value of "java.util.Map.get(Object)" is null at helpful_null_pointer_exception.Main.example(Main.java:15) at helpful_null_pointer_exception.Main.main(Main.java:9)
Сообщение говорит, где и почему произошло NPE.
Записи
Чтобы определить неизменяемый класс, можно действовать так:
public class Person { private final String name; private final String surname; private final Integer age; public Person(String name, String surname, Integer age) { this.name=name; this.surname=surname; this.age=age; } public String getName() { return name; } public String getSurname() { return surname; } public Integer getAge() { return age; } @Override public boolean equals(Object o) { if (this==o) return true; if (o==null || getClass() !=o.getClass()) return false; Person person=(Person) o; if (!Objects.equals(name, person.name)) return false; if (!Objects.equals(surname, person.surname)) return false; return Objects.equals(age, person.age); } @Override public int hashCode() { int result=name !=null ? name.hashCode() : 0; result=31 * result + (surname !=null ? surname.hashCode() : 0); result=31 * result + (age !=null ? age.hashCode() : 0); return result; } @Override public String toString() { return "Person{" + "name='" + name + ''' + ", surname='" + surname + ''' + ", age=" + age + '}'; } }
Получается довольно много строк. Упростим синтаксис, воспользовавшись библиотекой Lombok:
@Value public class Person { String name; String surname; Integer age; }
Не слишком расписывая код и не завися от стороннего решения, с помощью записей можно написать этот класс следующим образом:
public record Person(String name, String surname, Integer age) { }
Тип record создает неизменяемый класс с приватными финализированными полями, геттерами, методом toString, а также методами equals и hashCode.
Таким образом, есть три разных способа добиться одного и того же. В первом подходе мы были слишком многословны, а во втором — зависели от стороннего решения. При третьем код намного короче, и мы не зависим от стороннего решения.
Запечатанные классы
При помощи этой функции класс может указывать, какие классы могут его расширять. Например:
public abstract sealed class Developer permits BackendDeveloper, FrontendDeveloper { }
Следующий код не будет компилироваться с приведенным выше определением:
public final class ProductManager extends Developer { }
Этот код не компилируется, потому что класс Developer разрешает расширять себя только классам BackendDeveloper и FrontendDeveloper. Однако его пытается расширить класс ProductManager. И возникает ошибка компиляции.
Такие классы, вероятно, больше всего пригодятся разработчикам библиотек и фреймворков.
Сопоставление с образцом для switch
Для этого объяснения используется пример с сайта OpenJDK.
Вот код:
static String formatter(Object o) { String formatted="unknown"; if (o instanceof Integer i) { formatted=String.format("int %d", i); } else if (o instanceof Long l) { formatted=String.format("long %d", l); } else if (o instanceof Double d) { formatted=String.format("double %f", d); } else if (o instanceof String s) { formatted=String.format("String %s", s); } return formatted; }
При сопоставлении с образцом switch:
static String formatterPatternSwitch(Object o) { return switch (o) { case Integer i -> String.format("int %d", i); case Long l -> String.format("long %d", l); case Double d -> String.format("double %f", d); case String s -> String.format("String %s", s); default -> o.toString(); }; }
Мы сопоставили объект на основе его типа и не выполняли никакого явного приведения.
Эту функцию можно объединить с запечатанными классами. Так получится писать точные и довольно емкие switch-выражения.
Бонус
До Java 16 мы собирали потоки в виде списка, как показано ниже:
List<Integer> digits=List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); List<Integer> evenDigits=digits.stream() .filter(digit -> digit % 2==0) .collect(Collectors.toList());
Теперь для той же цели можно вызвать новый метод ToList для потоков:
List<Integer> digits=List.of(0, 1, 2, 3, 4, 5, 6, 7, 8, 9); List<Integer> evenDigits=digits.stream() .filter(digit -> digit % 2==0) .toList();
Читайте также:
- 3 способа мониторинга изменений лог-файлов в Java
- Как правильно учиться Java-программированию: история одного тьютора
- String, StringBuilder и StringBuffer: понимаете ли вы разницу?
Читайте нас в Telegram, VK и Дзен
Перевод статьи Fatih İver: 7 Practical Java Enhancements from Java 11 to Java 17
Сообщение Java 17: что нового по сравнению с Java 11 появились сначала на NOP::Nuances of programming.