Java17新特性
文本块
switch表达式
record关键字
sealed classes密封类
instanceof模式匹配
Helpful NullPointerExceptions
日期周期格式化
精简数字格式化支持
Stream.toList()简化
文本块
在Java17之前的版本里,如果我们需要定义一个字符串,比如一个JSON数据我们需要 双引号需要进行转义、为了字符串的可读性需要通过+号连接、如果需要将JSON复制到代码中需要做大量的格式调整。
通过Java 17中的文本块语法,类似的字符串处理则会方便很多;通过三个双引号可以定义一个文本块,并且结束的三个双引号不能和开始的在同一行。
private static void textTest() {
// java8
String lowVersion = "{\n" +
" \"name\": \"yxy\",\n" +
" \"age\": 18\n" +
"}";
System.out.println(lowVersion);
// java17
String highVersion = """
{
"name":"yxy",
"age":18
}
""";
System.out.println(highVersion);
}
switch表达式
Java 17版本中switch表达式将允许switch有返回值,并且可以直接作为结果赋值给一个变量,等等一系列的变化。
省略break也可以防止穿透:可以通过switch表达式来进行简化,将冒号(:)替换为箭头(->),并且switch表达式默认不会失败,所以不需要break。
switch表达式也可以返回一个值,也可以直接省略赋值动作直接打印
用yield返回结果
如果你想在case里想做不止一件事,比如在返回之前先进行一些计算或者打印操作,可以通过大括号来作为case块,最后的返回值使用关键字yield进行返回。
private static void switchTest() {
FruitEnum fruitEnum = FruitEnum.PEAR;
// 防止穿透
switch (fruitEnum) {
case APPLE, PEAR -> System.out.println("apple and pear");
case MANGO -> System.out.println("mango");
default -> System.out.println("error");
}
// 返回一个值
String text = switch (fruitEnum) {
case APPLE, PEAR -> "apple and pear";
case MANGO -> "mango";
default -> "error";
};
System.out.println(text);
// yield返回结果
System.out.println(switch (fruitEnum) {
case APPLE, PEAR :
yield "apple and pear";
case MANGO:
yield "mango";
default:
yield "error";
});
// 代码块
String text2 = switch (fruitEnum) {
case APPLE, PEAR -> {
System.out.println("one");
yield "apple and pear";
}
case MANGO -> {
System.out.println("two");
yield "mango";
}
default -> "error";
};
System.out.println(text2);
}
record关键字
record用于创建不可变的数据类。在这之前如果你需要创建一个存放数据的类,通常需要先创建一个Class,然后生成构造方法、getter、setter、hashCode、equals和toString等这些方法,或者使用Lombok来简化这些操作。
我们来通过Person类做一些测试,比如创建两个对象,对他们进行比较,打印这些操作。
假设有一些场景我们只需要对Person的name和age属性进行打印,在有record之后将会变得非常容易。
简单应用
private static void personRecordTest() {
// java8
Person person1 = new Person("yxy", 18, "北京");
Person person2 = new Person("yxy", 18, "北京");
System.out.println(person1);
System.out.println(person2);
System.out.println(person1.equals(person2));
// java11 使用record定义
record PersonRecord(String name, int age){}
PersonRecord personRecord = new PersonRecord(person1.getName(), person1.getAge());
PersonRecord personRecord1 = new PersonRecord(person2.getName(), person2.getAge());
System.out.println(personRecord);
System.out.println(personRecord1);
System.out.println(personRecord1.equals(personRecord));
}
record同样也有构造方法,可以在构造方法中对数据进行一些验证操作。
private static void personRecordTest() {
// java8
Person person1 = new Person("yxy", 18, "北京");
Person person2 = new Person("yxy", 18, "北京");
System.out.println(person1);
System.out.println(person2);
System.out.println(person1.equals(person2));
// java11 使用record定义
record PersonRecord(String name, int age){
// 构造方法校验属性value
PersonRecord {
if (age < 20) {
throw new RuntimeException("error");
}
}
}
PersonRecord personRecord = new PersonRecord(person1.getName(), person1.getAge());
PersonRecord personRecord1 = new PersonRecord(person2.getName(), person2.getAge());
System.out.println(personRecord);
System.out.println(personRecord1);
System.out.println(personRecord1.equals(personRecord));
}
record类
public record PersonRecord(String name, int age) {
}
private static void personRecordTest2() {
PersonRecord personRecord = new PersonRecord("yxy", 20);
// .name()和getName()的效果是一样的
System.out.println(personRecord.name());
System.out.println(personRecord.age());
System.out.println(personRecord);
}
getName()变成了name,但是功能一样的,只是命名方式变了
因为是不可变数据类型,没有set方法
自动toString,虽然最后结果不一样,但是可以正常看,不是地址值
自动实现equals,如果不覆盖这个方法,两个对象比较时候会比较指向的对象是不是同一个对象,这个只是比了里面的值是否相等。
record中可以覆盖构造方法、创建静态方法、定义自己的方法
public record PersonRecord(String name, int age) {
// 重写构造方式
public PersonRecord(String name, int age) {
if (age > 18) {
throw new RuntimeException("error");
}
this.name = name;
this.age = 1;
}
//额外定义的方法
public String nameToUppercase() {
return this.name.toUpperCase();
}
//静态方法
public static String nameAddPassword(PersonRecord user1) {
return user1.name + user1.age;
}
}
private static void personRecordTest2() {
PersonRecord personRecord = new PersonRecord("yxy", 10);
// .name()和getName()的效果是一样的
System.out.println(personRecord.name());
System.out.println(personRecord.age());
System.out.println(personRecord);
System.out.println(personRecord.nameToUppercase());
System.out.println(PersonRecord.nameAddPassword(personRecord));
}
密封类 sealed class
简介
密封类(class)和接口(interface)限制哪些其他类或接口可以扩展(extends)或实现(implements)它们。
密封类JDK 15 中作为预览功能提供,并在JDK 16 中作为预览功能进行了改进。
现在在 JDK 17 中,密封类被最终确定,与 JDK 16 没有任何变化。
允许类(class)或接口(interface)的作者控制负责实现它的代码。
提供比访问修饰符更具声明性的方式来限制超类的使用。
支持模式匹配(例如switch模式匹配中的案例)
语法
sealed class 父类 permits 子类1,子类2 ....
表示父类是密封类,指定可以被继承的有子类1,子类2 ...
permits
指定的子类必须在父类的附近在同一个模块中(module jdk9新增),(父类在一个命名的模块中)
或在同一包中(package),(父类在一个未命名的模块)
子类在大小和数量上都很小时,可以同父类定义在一个java文件中
permits
指定的子类必须直接继承该父类permits
指定的子类在定义时必须使用以下三种修饰符中的一种final:表示该子类是最终的,不能被继承
sealed:表示该子类是密封类,可以被指定的其他类继承
non-sealed:表示该子类是非密封类,可以被任意其他类继承
抽象类Animal 由sealed修饰,只能由permits指定的子类或接口来继承或实现。
package cn.test.sealed.domain;
public abstract sealed class Animal permits Cat, Dog, Pig {
public abstract void eat();
}
抽象类Animal 由sealed修饰,说明它的子类会受到限制,只能是permits子句中的子类的其中一个。final表示这个子类不能再被继承了。
package cn.test.sealed.domain;
public final class Dog extends Animal {
@Override
public void eat() {
System.out.println("dog eat");
}
}
类Cat由sealed修饰,只能由permits指定的子类或接口来继承或实现。
package cn.test.sealed.domain;
public sealed class Cat extends Animal permits BlackCat {
@Override
public void eat() {
System.out.println("cat eat");
}
}
package cn.test.sealed.domain;
public final class BlackCat extends Cat {
@Override
public void eat() {
System.out.println("black cat eat");
}
}
抽象类Animal 由sealed修饰,说明它的子类会受到限制,只能是permits子句中的子类的其中一个。non-sealed表示这个子类没有密封限制,随便继承
package cn.test.sealed.domain;
public non-sealed class Pig extends Animal {
@Override
public void eat() {
System.out.println("pig eat");
}
}
package cn.test.sealed.domain;
public class BigPig extends Pig{
}
调用
private static void sealedClassTest() {
Animal cat = new Cat();
cat.eat();
Animal blackCat = new BlackCat();
blackCat.eat();
Animal pig = new Pig();
pig.eat();
Animal bigPig = new BigPig();
bigPig.eat();
Animal dog= new Dog();
dog.eat();
}
接口Animal 由sealed修饰,只能由permits指定的子类或接口来继承或实现。
package cn.test.sealed.interfaces;
public sealed interface Animal permits Cat, Dog, Pig {
void eat();
}
接口Animal由sealed修饰,说明它的子类会受到限制,只能是permits子句中的子类的其中一个。final表示这个子类不能再被继承了。
package cn.test.sealed.interfaces;
public final class Dog implements Animal {
@Override
public void eat() {
System.out.println("impl dog eat");
}
}
接口Animal由sealed修饰,说明它的子类会受到限制,只能是permits子句中的子类的其中一个。non-sealed表示这个子类没有密封限制,随便继承
package cn.test.sealed.interfaces;
public non-sealed class Cat implements Animal {
@Override
public void eat() {
System.out.println("impl cat eat");
}
}
接口Pig由sealed修饰,只能由permits指定的子类或接口来继承或实现。
package cn.test.sealed.interfaces;
public sealed class Pig implements Animal permits BlackPig, White {
@Override
public void eat() {
System.out.println("impl pig eat");
}
}
接口Pig由sealed修饰,说明它的子类会受到限制,只能是permits子句中的子类的其中一个。non-sealed表示这个子类没有密封限制,随便继承
package cn.test.sealed.interfaces;
public non-sealed class White extends Pig{
}
接口Pig 由sealed修饰,说明它的子类会受到限制,只能是permits子句中的子类的其中一个。final表示这个子类不能再被继承了。
package cn.test.sealed.interfaces;
public final class BlackPig extends Pig {
@Override
public void eat() {
System.out.println("impl black pig eat");
}
}
调用
private static void sealedInterfaceTest() {
cn.itbox.sealed.interfaces.Animal dog = new cn.itbox.sealed.interfaces.Dog();
dog.eat();
cn.itbox.sealed.interfaces.Animal cat = new cn.itbox.sealed.interfaces.Cat();
cat.eat();
cn.itbox.sealed.interfaces.Animal pig = new cn.itbox.sealed.interfaces.Pig();
pig.eat();
cn.itbox.sealed.interfaces.Animal blackPig = new cn.itbox.sealed.interfaces.BlackPig();
blackPig.eat();
cn.itbox.sealed.interfaces.Animal whitePig = new cn.itbox.sealed.interfaces.White();
whitePig.eat();
}
JDK中的密封类
ConstantDesc是JDK12 中出现的一组API。定义了一些JVM中已知的符号引用,常量池中的常量描述符
JDK17 使用密封类对已知的继承体系做了优化
public sealed interface ConstantDesc
permits ClassDesc,
MethodHandleDesc,
MethodTypeDesc,
Double,
DynamicConstantDesc,
Float,
Integer,
Long,
String {
....
}
swich对sealed的支持
Todo
instanceof模式匹配
通常我们使用instanceof时,一般发生在需要对一个变量的类型进行判断,如果符合指定的类型,则强制类型转换为一个新变量。
Object o = new RedApple();
if (o instanceof RedApple) {
RedApple redApple = (RedApple) o;
System.out.println(redApple.getName());
}
在使用instanceof的模式匹配后,上面的代码可进行简写。
if (o instanceof RedApple redApple) {
System.out.println(redApple.getName());
}
可以将类型转换和变量声明都在if中处理。同时,可以直接在if中使用这个变量。
if (o instanceof RedApple redApple && redApple.getName().equals("apple")) {
System.out.println(redApple.getName());
}
因为只有当instanceof的结果为true时,才会定义变量furit,所以这里可以使用&&,但是改为||就会编译报错。
Helpful NullPointerExceptions
Helpful NullPointerExceptions可以在我们遇到NullPointerExceptions时节省一些分析时间。
如下的代码会导致一个NPE
String str = null;
int length = str.length();
在Java 11中,输出将显示NullPointerException发生的行号,但不知道哪个方法调用时产生的null,必须通过调试的方式找到。
Exception in thread "main" java.lang.NullPointerException
at com.heiz.java17.HelpfulNullPointerExceptionsDemo.main(HelpfulNullPointerExceptionsDemo.java:13)
在Java 17中,则会准确显示发生NullPointerException的位置
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "String.length()" because "str" is null
at cn.itbox.controller.TestController.main(TestController.java:39)
日期周期格式化
在Java 17中添加了一个新的模式B,用于格式化DateTime,它根据Unicode标准指示一天时间段。
private static void dataTimeFormatBTest() {
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)));
}
上午
下午
晚上
晚上
午夜
精简数字格式化支持
在NumberFormat中添加了一个工厂方法,可以根据Unicode标准以紧凑的、人类可读的形式格式化数字。
SHORT格式如下所示:
// NumberFormat fmt = NumberFormat.getCompactNumberInstance(Locale.CHINESE, NumberFormat.Style.SHORT);
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
LONG格式如下所示:
fmt = NumberFormat.getCompactNumberInstance(Locale.CHINESE, NumberFormat.Style.LONG);
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,需要通过调用collect方法使用Collectors.toList(),代码非常冗长。
在Java 17中将会变得简单,可以直接调用toList()。
private static void streamListTest() {
// java8
Stream<Integer> integerStream = Stream.of(1, 2, 3);
List<Integer> collect = integerStream.collect(Collectors.toList());
for (Integer i : collect) {
System.out.println(i);
}
// java11
Stream<Integer> integerStream2 = Stream.of(1, 2, 3);
List<Integer> list = integerStream2.toList();
for (Integer i : list) {
System.out.println(i);
}
}
代码参考
https://gitee.com/yixiuyu/java17.git
Java17与Java8对比
基本信息
Java 17与Java 8是Java版本中的两个重要里程碑。Java 8是Java版本中的一次重大更新,于2014年发布,引入了很多新的特性和功能,包括Lambda表达式、Stream API、函数式接口等。Java 17是Java SE 17版本,于2021年9月发布,是Java SE 16的长期支持(LTS)版本。Java 17中也有一些新的特性和改进,我们将在后文中详细讨论。
性能比较
Java 17与Java 8在性能方面的比较非常重要。Java 8引入了一些性能改进,例如优化了字符串连接和数组排序等操作。Java 17在性能方面也有一些新的改进,例如:
改进了JIT编译器,提高了应用程序的性能。
改进了垃圾回收器,提高了垃圾回收的效率和吞吐量。
引入了C++风格的内存管理,包括对堆内存分配的优化和对垃圾回收的改进。
这些改进都可以提高Java应用程序的性能和响应速度。
语言特性比较
Java 8引入了一些新的语言特性,例如Lambda表达式和函数式接口。这些特性让Java程序员能够使用函数式编程的方式编写代码,从而使得代码更加简洁、易读、易维护。Java 17在语言特性方面也有一些新的改进,例如:
引入了Sealed类,这是一种新的类修饰符,用于限制类的继承。这样可以使得代码更加安全、可维护。
引入了Pattern Matching for Switch语法,这是一种新的switch语法,可以用于模式匹配。这样可以使得代码更加简洁、易读、易维护。
引入了Record类,这是一种新的数据类,可以用于定义只有属性和访问器的简单数据对象。这样可以使得代码更加简洁、易读、易维护。
这些改进都可以使得Java程序员能够使用更加先进、更加高效的语言特性编写代码。
应用场景比较
Java 8和Java 17都可以用于不同的应用场景,但是它们在一些方面有所不同。Java 8适用于开发中小型应用程序和Web应用程序,例如Web服务、企业级应用程序和桌面应用程序等。Java 8也可以用于开发大型应用程序,但是在大型应用程序中可能会出现一些性能问题。Java 17则更适合用于开发大型应用程序和高性能应用程序,例如高性能计算、云计算、大数据处理等。
参考
10 w+字总结!Java 8---Java 17 特性详解
Java 17 VS Java 8: 新旧对决,这些Java 17新特性你不容错过_java17-CSDN博客
Java17新特性及代码示例:还在使用Java8? 这5个Java17新功能,你会喜欢的
jdk17新特性—— 密封类(Sealed Classes)_jdk17密封类-CSDN博客
评论区