Эта статья участвовала в "Проект «Звезда раскопок»”, чтобы выиграть творческий подарочный пакет и бросить вызов творческим поощрительным деньгам.
Ставьте лайки и смотрите снова, формируйте привычку, а комментарии могут участвовать в лотерее вокруг Наггетс.
предисловие
Java 17 выйдет 14 сентября 2021 года. Как трудовой мигрант в новую эпоху, необходимо знать, какие новые вещи доступны.
Java 17 — это еще один выпуск LTS (долгосрочная поддержка) после Java 11. Полный список JEP (предложений по улучшению Java), которые изменились между Java 11 и Java 17, можно найти на веб-сайте OpenJDK.
В этой статье основное внимание будет уделено обновлению Java 17 с точки зрения синтаксиса и использованию некоторых примеров кода, чтобы сделать его более понятным для всех, в основном с учетом следующих 9 пунктов:
- текстовый блок
- переключить выражение
- Записать ключевое слово
- запечатанные классыЗапечатанные классы
- сопоставление с образцом instanceof
- Helpful NullPointerExceptions
- Форматирование цикла даты
- Ограниченная поддержка форматирования чисел
- Stream.toList() упрощенный
текстовый блок
В версии до Java17, если нам нужно определить строку, например данные JSON, в основном все следующим образом:
public void lowVersion() {
String text = "{\n" +
" \"name\": \"小黑说Java\",\n" +
" \"age\": 18,\n" +
" \"address\": \"北京市西城区\"\n" +
"}";
System.out.println(text);
}
Есть несколько проблем с этим определением:
- Двойные кавычки необходимо экранировать;
- Для удобочитаемости строки ее нужно соединить знаком +;
- Если вам нужно скопировать JSON в код, вам нужно сделать много форматирования (конечно, это можно решить и другими инструментами);
С синтаксисом текстового блока в Java 17 подобная обработка строк намного удобнее; текстовый блок может быть определен тремя двойными кавычками, а три двойных кавычки в конце не могут быть на той же строке, что и начало.
JSON в приведенном выше примере может быть более удобным и читаемым с помощью определения текстового блока. код показывает, как показано ниже:
private void highVersion() {
String text = """
{
"name": "小黑说Java",
"age": 18,
"address": "北京市西城区"
}
""";
System.out.println(text);
}
Вывод этого кода:
{
"name": "小黑说Java",
"age": 18,
"address": "北京市西城区"
}
переключить выражение
Выражение switch в Java 17 позволяет переключателю иметь возвращаемое значение и может напрямую присваивать результат переменной и т. д.
Вот пример переключателя, который, в зависимости от заданного значения перечисления, выполняет операцию case, намеренно опущеннуюbreak
.
private static void lowVesion(Fruit fruit) {
switch (fruit) {
case APPLE, PEAR:
System.out.println("普通水果");
case MANGO, AVOCADO:
System.out.println("进口水果");
default:
System.out.println("未知水果");
}
}
Мы называем этот метод передачей вAPPLE
, который выводит следующие результаты:
普通水果
进口水果
未知水果
Очевидно, что это не желаемый результат, потому что нам нужно добавить перерыв на каждый CASSE, чтобы предотвратить выполнение всех случаев.
private static void lowVesion(Fruit fruit) {
switch (fruit) {
case APPLE, PEAR:
System.out.println("普通水果");
break;
case MANGO, AVOCADO:
System.out.println("进口水果");
break;
default:
System.out.println("未知水果");
}
}
Это можно упростить с помощью выражения switch. Замените двоеточия (:) стрелками (->), и выражения переключения по умолчанию не завершатся ошибкой, поэтому разрыв не нужен.
private static void withSwitchExpression(Fruit fruit) {
switch (fruit) {
case APPLE, PEAR -> System.out.println("普通水果");
case MANGO, AVOCADO -> System.out.println("进口水果");
default -> System.out.println("未知水果");
}
}
Выражение переключателя также может возвращать значение, например, в приведенном выше примере мы можем заставить переключатель возвращать строку, представляющую текст, который мы хотим напечатать. Обратите внимание, что в конце оператора switch добавляется точка с запятой.
private static void withReturnValue(Fruit fruit) {
String text = switch (fruit) {
case APPLE, PEAR -> "普通水果";
case MANGO, AVOCADO -> "进口水果";
default -> "未知水果";
};
System.out.println(text);
}
Вы также можете напрямую опустить действие присваивания и распечатать его напрямую.
private static void withReturnValue(Fruit fruit) {
System.out.println(switch (fruit) {
case APPLE, PEAR -> "普通水果";
case MANGO, AVOCADO -> "进口水果";
default -> "未知水果";
});
}
Если вы хотите сделать несколько действий в случае, например, выполнить некоторые вычисления или распечатать перед возвратом, вы можете использовать фигурные скобки в качестве блока варианта и использовать ключевые слова для окончательного возвращаемого значения.yield
возвращаться.
private static void withYield(Fruit fruit) {
String text = switch (fruit) {
case APPLE, PEAR -> {
System.out.println("给的水果是: " + fruit);
yield "普通水果";
}
case MANGO, AVOCADO -> "进口水果";
default -> "未知水果";
};
System.out.println(text);
}
Результат этого:
给的水果是: APPLE
普通水果
Конечно, вы также можете напрямую использовать результаты Yield return.
private static void oldStyleWithYield(Fruit fruit) {
System.out.println(switch (fruit) {
case APPLE, PEAR:
yield "普通水果";
case MANGO, AVOCADO:
yield "进口水果";
default:
yield "未知水果";
});
}
ключевое слово записи
запись используется для создания неизменяемых классов данных. Перед этим, если вам нужно создать класс для хранения данных, вам обычно нужно сначала создать класс, а затем сгенерировать такие методы, как конструкторы, геттеры, сеттеры, hashCode, equals и toString, или использовать Lombok для упрощения этих операций.
Например, определите класс Person:
// 这里使用lombok减少代码
@Data
@AllArgsConstructor
public class Person {
private String name;
private int age;
private String address;
}
Давайте проведем несколько тестов с классом Person, например, создадим два объекта, сравним их и напечатаем эти операции.
public static void testPerson() {
Person p1 = new Person("小黑说Java", 18, "北京市西城区");
Person p2 = new Person("小白说Java", 28, "北京市东城区");
System.out.println(p1);
System.out.println(p2);
System.out.println(p1.equals(p2));
}
Предположим, что есть некоторые сценарии, в которых нам нужно только напечатать атрибуты имени и возраста человека, это станет очень легко после того, как будет запись.
public static void testPerson() {
Person p1 = new Person("小黑说Java", 18, "北京市西城区");
Person p2 = new Person("小白说Java", 28, "北京市东城区");
// 使用record定义
record PersonRecord(String name,int age){}
PersonRecord p1Record = new PersonRecord(p1.getName(), p1.getAge());
PersonRecord p2Record = new PersonRecord(p2.getName(), p2.getAge());
System.out.println(p1Record);
System.out.println(p2Record);
}
Запись также может быть определена отдельно как определение файла, но поскольку использование записи очень компактно, ее можно определить непосредственно там, где ее необходимо использовать.
запись также имеет метод построения, и вы можете выполнять некоторые операции проверки данных в методе построения.
public static void testPerson() {
Person p1 = new Person("小黑说Java", 18, "北京市西城区");
Person p2 = new Person(null, 28, "北京市东城区");
record PersonRecord(String name, int age) {
// 构造方法
PersonRecord {
System.out.println("name " + name + " age " + age);
if (name == null) {
throw new IllegalArgumentException("姓名不能为空");
}
}
}
PersonRecord p1Record = new PersonRecord(p1.getName(), p1.getAge());
PersonRecord p2Record = new PersonRecord(p2.getName(), p2.getAge());
}
закрытый класс
Запечатанные классы дают нам больший контроль над тем, какие классы могут расширять классы, которые я определяю. Запечатанные классы могут быть более полезными для разработчиков фреймворков или промежуточного программного обеспечения. До этого класс мог быть либо расширяемым, либо окончательным, и вариантов было всего два.
Запечатанные классы могут контролировать, какие классы могут наследоваться от суперклассов.До Java 17, если нам нужно контролировать, какие классы могут наследовать, мы можем изменить уровень доступа класса, например, удалить общедоступность класса, а уровень доступа — это дефолт. Например, мыcom.heiz.java11
Пакет определяет следующие три класса:
package com.heiz.java11;
public abstract class Fruit {
}
public class Apple extends Fruit {
}
public class Pear extends Fruit {
}
тогда мы можем сделать это в другом пакетеcom.heiz123.java11
Напишите следующий код в:
private static void test() {
Apple apple = new Apple();
Pear pear = new Pear();
Fruit fruit = apple;
class Avocado extends Fruit {};
}
Можно определитьApple
,Pear
, вы также можете использоватьapple
экземпляр, назначенныйFruit
, и может бытьFruit
Наследовать.
если мы не хотимFruit
существуетcom.heiz.java11
Расширения вне пакета, только права доступа в версии Java11, удалите общедоступный модификатор класса. Таким образом, хотя его можно контролировать, он также может привести кFruit fruit = apple;
Также не компилируется, в Java 17 это решается запечатыванием класса.
package com.heiz.java17;
public abstract sealed class Fruit permits Apple,Pear {
}
public non-sealed class Apple extends Fruit {
}
public final class Pear extends Fruit {
}
по определениюFruit
по ключевому словуsealedобъявлен как закрытый класс, черезpermitsможно указатьApple,Pear
Классы могут быть расширены путем наследования.
Подклассы должны указывать на то, что он является окончательным, не запечатанным или герметичным. Родительский класс не может контролировать, может ли подкласс наследоваться.
private static void test() {
Apple apple = new Apple();
Pear pear = new Pear();
// 可以将apple赋值给Fruit
Fruit fruit = apple;
// 只能继承Apple,不能继承Fruit
class Avocado extends Apple {};
}
сопоставление с образцом instanceof
Обычно, когда мы используем instanceof, это обычно происходит, когда нам нужно судить о типе переменной, и если он соответствует указанному типу, тип приводится к новой переменной.
private static void oldStyle(Object o) {
if (o instanceof Fruit) {
Fruit fruit = (GrapeClass) o;
System.out.println("This fruit is :" + fruit.getName);
}
}
Приведенный выше код можно сократить после сопоставления с образцом с помощью instanceof.
private static void oldStyle(Object o) {
if (o instanceof Fruit fruit) {
System.out.println("This fruit is :" + fruit.getName);
}
}
В операторе if можно обрабатывать как преобразования типов, так и объявления переменных. В то же время эту переменную можно использовать непосредственно в операторе if.
private static void oldStyle(Object o) {
if (o instanceof Fruit fruit && fruit.getColor()==Color.RED) {
System.out.println("This fruit is :" + fruit.getName);
}
}
потому что только когдаinstanceof
Результатtrue
Когда переменная определенаfruit
, так что здесь вы можете использовать&&
, но вместо||
будет компилировать ошибку.
Helpful NullPointerExceptions
Полезные исключения NullPointerException могут сэкономить время анализа, когда мы сталкиваемся с NPE. Следующий код приведет к NPE.
public static void main(String[] args) {
Person p = new Person();
String cityName = p.getAddress().getCity().getName();
}
В Java 11 на выходе будет показан номер строки, в которой произошло исключение NullPointerException, но я не знаю, какой вызов метода привел к нулевому значению, и его необходимо найти путем отладки.
Exception in thread "main" java.lang.NullPointerException
at com.heiz.java17.HelpfulNullPointerExceptionsDemo.main(HelpfulNullPointerExceptionsDemo.java:13)
В Java 17 отображается точное место, где произошел NPE.
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "com.heiz.java17.Address.getCity()" because the return value of "com.heiz.java17.Person.getAddress()" is null
at com.heiz.java17.HelpfulNullPointerExceptionsDemo.main(HelpfulNullPointerExceptionsDemo.java:13)
Форматирование цикла даты
В Java 17 добавлен новый режимB
, для форматированияDateTime
, он основан наUnicode
Стандарт указывает период времени в один день.
Используя стандартную английскую локаль, распечатайте несколько моментов дня:
DateTimeFormatter dtf = DateTimeFormatter.ofPattern("B");
System.out.println(dtf.format(LocalTime.of(8, 0)));
System.out.println(dtf.format(LocalTime.of(13, 0)));
System.out.println(dtf.format(LocalTime.of(20, 0)));
System.out.println(dtf.format(LocalTime.of(23, 0)));
System.out.println(dtf.format(LocalTime.of(0, 0)));
Выходной результат:
in the morning
in the afternoon
in the evening
at night
midnight
В случае китайской локали вывод:
上午
下午
晚上
晚上
午夜
Видно, что наш вечер включает в себя британские и американские страныevening
иnight
из.
Ограниченная поддержка форматирования чисел
В NumberFormat добавлен фабричный метод для форматирования чисел в компактной, удобочитаемой форме в соответствии со стандартом Unicode.
КРАТКИЙ формат выглядит следующим образом:
NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.SHORT);
System.out.println(fmt.format(1000));
System.out.println(fmt.format(100000));
System.out.println(fmt.format(1000000));
Выходной формат:
1K
100K
1M
ДЛИННЫЙ формат выглядит следующим образом:
fmt = NumberFormat.getCompactNumberInstance(Locale.ENGLISH, NumberFormat.Style.LONG);
System.out.println(fmt.format(1000));
System.out.println(fmt.format(100000));
System.out.println(fmt.format(1000000));
Результат:
1 thousand
100 thousand
1 million
Stream.toList()
Если вам нужно преобразовать Stream в List, вам нужно использовать Collectors.toList(), вызвав метод collect, а код очень подробный.
private static void oldStyle() {
Stream<String> stringStream = Stream.of("a", "b", "c");
List<String> stringList = stringStream.collect(Collectors.toList());
for(String s : stringList) {
System.out.println(s);
}
}
В Java 17 будет просто вызывать напрямуюtoList()
.
private static void streamToList() {
Stream<String> stringStream = Stream.of("a", "b", "c");
List<String> stringList = stringStream.toList();
for(String s : stringList) {
System.out.println(s);
}
}
Наконец
В этом выпуске вы кратко ознакомитесь с некоторыми функциями синтаксиса, добавленными со времени последней версии LTS, Java 11. В дополнение к этим грамматическим обновлениям в Java 17 также есть множество функций, связанных с JVM, библиотеками, инструментами упаковки и т. д. Я не буду представлять их вам здесь по одному.Официальный сайт OpenJDKсмотреть.