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
1MLONG格式如下所示:
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 millionStream.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.gitJava17与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博客
评论区