前言
Java 17 是 2021 年 9 月 14 日正式发布的,距今也已经快2年多了,是一个长期支持(LTS)版本。Java 17 这个版本非常重要,Spring Framework 6.0 和 Spring Boot 3.0 最低支持都是 Java 17,搞Java开发肯定是离不开Spring这个主流的框架。下面这张图是 Oracle 官方给出的 Oracle JDK 支持的时间线,可以看得到Java17支持到2029年9月。
增强Switch
之前的 switch 写法如下:
代码语言:javascript复制String a = "jdk17";
switch (a){
case "jdk8":
System.out.println("我是jdk8");
break;
case "jdk17":
System.out.println("我是jdk17");
break;
default:
System.out.println("未知");
break;
}
从jdk12后可以通过 switch 表达式来进行简化。使用箭头“->”,并且不需要每个 case 都 break,大大提高了我们的编写效率,如下所示:
代码语言:javascript复制switch (a) {
case "jdk8" -> System.out.println("我是jdk8");
case "jdk17" -> System.out.println("我是jdk17");
default -> System.out.println("未知");
}
当我们需要赋值给一个变量的时候,不需要在每个case里头挨个赋值,其实 switch 也可以返回一个值,例如:
代码语言:javascript复制String a = "jdk17";
String who = switch (a) {
case "jdk8" -> "我是jdk8";
case "jdk17" -> "我是jdk17";
default -> "未知";
};
System.out.println(who);
输出:
代码语言:javascript复制我是jdk17
如果是匹配多个case 的话用逗号分开,例如:
代码语言:javascript复制String a = "jdk17";
String who = switch (a) {
case "jdk8","jdk17" -> "我是jdk家族";
case "spring","spring boot" -> "我是spring家族";
default -> "未知";
};
System.out.println(who);
如果你想在case里做不止一件事,比如在返回之前先进行一些计算或者打印操作。可以通过大括号来作为case块,最后的返回值使用关键字 yield 进行返回。
代码语言:javascript复制String a = "spring";
String who = switch (a) {
case "jdk8","jdk17" -> {
System.out.println(1 1);
yield "我是jdk家族";
}
case "spring","spring boot" -> {
System.out.println(2 2);
yield "我是spring家族";
}
default -> "未知";
};
System.out.println(who);
输出:
代码语言:javascript复制4
我是spring家族
以及之前的switch只支持 数值和字符串常量的匹配 ,而现在还支持对对象的类型来进行匹配,例如:
代码语言:javascript复制Object a = 888;
String who = switch (a) {
case null -> "is null";
case Integer i -> String.format("i is %s",i);
case String s -> String.format("s is string %s",s);
default -> a.toString();
};
System.out.println(who);
输出:
代码语言:javascript复制i is 888
文本块
在Java17之前的版本里,如果我们需要定义一个字符串,比如一个JSON数据或者一些html等,基本都是采用拼接的方式去定义,大量的加号和转义的双引号非常的恶心且难看,例如:
代码语言:javascript复制String text = "{n"
" "name": "小黑说Java",n"
" "age": 18,n"
" "address": "北京市西城区"n"
"}";
而从jdk15后这个写法才得以改善,只需要在前后加上 """ 。
代码语言:javascript复制String text = """
{
"name": "小黑说Java",
"age": 18,
"address": "北京市西城区"
}
""";
显然,更方便,更简洁了。若是在文本块内加入空格或者换行之类的,请参考以下:
代码语言:javascript复制s:表示空格
:表示不换行
s:表示添加一个空格且不换行
instanceof增强
通常我们使用instanceof时,一般发生在需要对一个变量的类型进行判断,如果符合指定的类型,则强制类型转换为一个新变量。
之前的写法:
代码语言:javascript复制List<?> rows = selectMenuList(menu, getUserId()).getRows();
List<SysMenu> menus = new ArrayList<>();
rows.forEach(r->{
if (r instanceof SysMenu)
menus.add((SysMenu)r);
});
jdk17后的写法:
代码语言:javascript复制List<?> rows = selectMenuList(menu, getUserId()).getRows();
List<SysMenu> menus = new ArrayList<>();
rows.forEach(r->{
if (r instanceof SysMenu sysMenu)
menus.add(sysMenu);
});
Record关键字
record用于创建不可变的数据类。在这之前如果你需要创建一个存放数据的类,通常需要先创建一个Class,然后生成构造方法、getter、setter、hashCode、equals和toString等这些方法,或者使用Lombok来简化这些操作。而record就基本相当于实体类 Lombok的效果,但是 record 是不支持继承的,适合数据量少且不复杂的情况下使用。
创建一个实体类 Pon:
代码语言:javascript复制@Data
@AllArgsConstructor
public class Pon {
private String name;
private String nickName;
private Integer addrNum;
}
然后进行一些简单的测试
代码语言:javascript复制public class JunitTest {
@Test
void test() {
Pon pon = new Pon("张三", "小三", 65220);
System.out.println(pon.getName());
System.out.println(pon.getNickName());
System.out.println(pon.getAddrNum());
}
}
输出:
代码语言:javascript复制张三
小三
65220
以下是使用record代替前面的Pon类,record的属性不需要去定义,直接通过参数的形式设置。
代码语言:javascript复制public record RecordPon(String name, String nikName, Integer addrNum) {
}
接下来对RecordPon进行一些简单的测试
代码语言:javascript复制public class JunitTest {
@Test
void test() {
RecordPon recordPon = new RecordPon("张三", "小三", 65220);
System.out.println(recordPon.name());
System.out.println(recordPon.nikName());
System.out.println(recordPon.addrNum());
RecordPon recordPon2 = new RecordPon("李四", "小四", 65220);
System.out.println(recordPon2.name());
System.out.println(recordPon2.nikName());
System.out.println(recordPon2.addrNum());
}
}
输出:
代码语言:javascript复制张三
小三
65220
李四
小四
65220
当然了,record 同样也有构造方法,可以在构造方法中对数据进行一些操作,例如:
代码语言:javascript复制public record RecordPon(String name, String nikName, Integer addrNum) {
public RecordPon {
if (Objects.equals(name, ""))
System.err.println( "name is not null");
}
}
总结:Lombok 与 record 是不同的工具,服务于不同的目的。此外,Lombok 更加灵活,它可以用于 record 受限的场景。
Helpful NullPointerExceptions
在 Java8 我们如果遇到NPE,通常只会输出将显示NullPointerException发生的行号,但不知道哪个方法调用时产生的null,必须通过调试的方式找到。对于复杂度高的代码来讲就非常耗时,而从Helpful NullPointerExceptions可以在我们遇到NPE时节省一些时间,会准确显示发生NPE的精确位置,比如以下代码会发生一个NPE:
代码语言:javascript复制public class JunitTest {
@Test
void test() {
String str = null;
int length = str.length();
}
}
输出:
密封类 sealed class
密封类是一种特殊的类,它用来表示受限的类继承结构,即一个类只能有有限的几种子类,而不能有任何其他类型的子类。这样可以让我们更好的控制哪些类可以对我定义的类进行扩展,而在这之前一个类要么是可以被extends的,要么是final的任何人都不能继承的,只有这两种选项,没有什么灵活性。首先创建Animal、Dog、Cat类:
代码语言:javascript复制public abstract class Animal {
public void put(){
System.out.println("我是动物");
}
}
代码语言:javascript复制public class Dog extends Animal {
}
代码语言:javascript复制public class Cat extends Animal {
}
在这里这个Animal 是可以被任何一个类继承的,例如:
代码语言:javascript复制public class JunitTest {
@Test
void test() {
Cat cat = new Cat();
Dog dog = new Dog();
Animal animal = cat;
Animal animal2 = dog;
class china extends Animal{}
class usa extends Dog{}
}
}
除了Dog和Cat类能继承之外,这里创建的china类也能继承Animal类,usa类也继承Dog类。ok这本来没什么问题,就是这个关系有点不正常,国家怎么能继承于动物呢?
在Jdk17中通过密封类可以解决这个问题,主要就这几个关键字:
- final:不允许被继承
- sealed:密封类(需要指定哪个类可以扩展它)
- non-sealed:可以被任何类继承
- permits :指定哪个类可以继承于我
现在我们将上面的Animal 类改成密封类,例如:
代码语言:javascript复制public sealed class Animal permits Dog{
public void put(){
System.out.println("我是动物");
}
}
此处的Animal是密封类,指定了只有Dog类可以继承它
代码语言:javascript复制public sealed class Dog extends Animal permits DogSon{
}
而此时Dog类也是一个密封类,它也指定了只有DogSon这个类可以继承它。这里做个小测试,创建一个BigDog类并继承Dog:
直接告诉你 密封层次结构中不允许使用"BigDog" ,需要给BigDog指定这个permits ,其他亦是如此,除非你定义成non-sealed。
备注????:密封类的子类必须是 final类、sealed类或non-sealed类,并且 父类和子类 必须在同一个包下
开始安装
下载安装可以参考我之前的文章:《Jdk17安装 环境配置详细教程【Windows》
总结
以上主要介绍了Jdk17的一些主流的新特性,更多具体特性请参考官方文档: https://www.oracle.com/java/technologies/javase/17-relnote-issues.html#NewFeature