前言
Jdk每半年都会出一个版本出来,而我们主要关注的就是LTS版本,翻译过来就是long term support(长期支持版本)。现在有两个新的LTS版本11和17,那我们肯定是优先选择最新的17版本。一些最新的框架对jdk的要求如Spring Framwork 6.x 对jdk的版本要求为17,Spring Boot 3.0开始对jdk最低版本要求为17,Kafaka最新版本3.0.0将不再支持jdk8版本等等。总而言之,言而总之,Jdk8将会渐渐淡出主流舞台,今后将是属于jdk17的天下了。
该篇主要是记录自己学习jdk17新特性的学习笔记,后续方便自己温习,也欢迎各位提出指导和意见。
几个新特性
1.对switch语句的增强
jdk17以前的switch语法如下(无法对null类型做匹配)
String beforeName = "刘邦";
String beforeAlias;
switch (beforeName) {
case "周瑜":
beforeAlias = "公瑾";
break;
case "徐庶":
case "yz":
beforeAlias = "元直";
break;
case "项羽":
System.out.println("三国");
beforeAlias = "西楚霸王";
break;
case "刘邦":
beforeAlias = "汉高祖";
break;
// jdk8中,这里不支持对null类型进行匹配
// case null:
// System.out.println("beforeName is null");
// break;
default:
beforeAlias = "未知";
}
System.out.println("beforeAlias:"+beforeAlias);
jdk17以后的switch语法如下,直接用 ->来代替上面繁琐的赋值操作,且以,来进行多个case条件匹配赋相同的值,除了简单的赋值优化以外,还可以在赋值前进行代码块操作,在{}中进行代码操作,最后通过yield关键字进行赋值。
String name = "项羽";
String alias = switch (name) {
case "周瑜" -> "公瑾";
case "徐庶","yz" -> "元直";
case "项羽" -> {
System.out.println("三国");
yield "楚霸王";
}
case "刘邦" -> "汉高祖";
default -> "未知";
};
System.out.println("alias:" + alias);
jdk17以前,对象类型的匹配只支持 数值和字符串常量 的匹配 jdk17以后switch还支持对 对象的 类型 做匹配,如下,还可对null类型做匹配。
Object o = new Object();
o = null;
String str = switch (o) {
case null -> "o is null";
case Integer i -> String.format("Integer is %s",i);
case Long l-> String.format("Long is %s",l);
case Double d -> String.format("Double is %s",d);
case String s -> String.format("String is %s",s);
default -> o.toString();
};
System.out.println(str);
2.处理恶心的字符串拼接
jdk17以前字符串拼接方式,看起来是不是很难受,很繁琐,一堆转义符 \t、\n的,修改起来也很麻烦,还容易出错。
String xmlStr = "<soapenv:Envelope xmlns:soapenv=\"http://schemas.xmlsoap.org/soap/envelope/\" xmlns:ser=\"http://services.xxx.com\">\n" +
"\t\t\t\t <soapenv:Header> \n" +
"\t\t\t\t \t<check>\n" +
"\t\t\t\t \t\t<caller>caller</caller>\n" +
"\t\t\t\t\t \t<pwd>pwd</pwd>\n" +
"\t\t\t\t\t \t<token>token</token>\n" +
"\t\t\t\t \t</check>\n" +
"\t\t\t\t </soapenv:Header>\n" +
"\t\t\t\t <soapenv:Body>\n" +
"\t\t\t\t <ser:configAPI>\n" +
"\t\t\t\t <!--Optional:-->\n" +
"\t\t\t\t <arg0>id</arg0>\n" +
"\t\t\t\t <!--Optional:-->\n" +
"\t\t\t\t <arg1>{\"kssj\":\"start\",\"jssj\":\"end\"}</arg1>\n" +
"\t\t\t\t </ser:configAPI>\n" +
"\t\t\t\t </soapenv:Body>\n" +
"\t\t\t\t</soapenv:Envelope>";
jdk17以后字符串拼接方式 ————在jdk15中已经提供了一个文本块的方式""" """
jdk17中对jdk15又进行增强,提供了两个 转义符 分别为 \和\s \: 置于行尾,用来将两行强制连接为一行 \s: 单个空白字符如下的字符串,看起来是不是简洁了许多,更加美观,换行也方便,不易出错。
String xmlStr2 = """
<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ser="http://services.xxx.com">" +
<soapenv:Header>\
<check>
<caller>%s</caller>\s
<pwd>pwd</pwd>
<token>%d</token>
</check>
</soapenv:Header>
<soapenv:Body>
<ser:configAPI>
<!--Optional:-->
<arg0>ret</arg0>
<!--Optional:-->
<arg1>{"kssj":"开始时间","jssj":"结束时间"}</arg1>
</ser:configAPI>
</soapenv:Body>
</soapenv:Envelope>
""";
//如果要在以上字符串中拼接内容的话,必须要用String的format方法,然后通过占位符替换的方式进行拼接
System.out.println(String.format(xmlStr2,"1111",1232433));
打印出来的效果如下,加\的地方,两行代码被拼接成同一行了,然后</caller>后面多出了一个可被选中的空白字符。
这个新特性我觉得在平时工作中还是有很大帮助的。
3.instanceof增强
jdk17以前 instanceof使用方式,需要 强转换
Object obj = "1";
if (obj instanceof Integer) {
Integer num = (Integer) obj;
System.out.println(num);
System.out.println("Integer");
} else {
String str = (String) obj;
System.out.println(str);
System.out.println("String");
}
jdk17以后 instanceof 新的使用方式,在jdk14的预览版中就有了,那什么是预览版呢?
所谓预览版,其实也就是在前面的16版本中已经有这个功能了,但是没有正式投入使用,那么在jdk17中也是可以正常使用的。
如下代码中,if括号中的 obj2 instanceof Integer num2,意思是如果obj2对象如果是Integer类型的实例或其子类的话,就直接将obj2对象转化为Integer类型,然后直接赋值给num2,然后sout进行输出。这种写法看起来是不是简便多了,省了不少操作。
Object obj2 = "12121";
if (obj2 instanceof Integer num2) {
System.out.println(num2);
System.out.println("Integer");
} else if (obj2 instanceof String str2) {
System.out.println(str2);
System.out.println("String");
}
4. 限制子类继承,防止子类变父类(密封类)
几个概念梳理
sealed关键字:
可以作用于父类、接口前,表示该类或接口为 密封类或密封接口
permits关键字:
作用于子类前面,表示限制某个密封类的子类类型必须为哪些类型,只有permits修饰的子类才可以且只能直接继承(不能间接继承)对应的密封类(父类),而其他类不能继承该父类。
说的有点拗口,下面通过代码来解释。
密封类(父类),Animal类
如下 Animal类通过sealed关键字修饰,则Animal类为密封类,在通过permits关键字修饰Cat、Dog类,则只有Cat、Dog类才可以且他们也只能继承Animal类。
public sealed class Animal permits Cat,Dog {
}
注意事项:Animal类和继承它的子类,必须在同一个包下面,不在同一个包下的子类,不能继承密封类或接口,如下图
既然父类有对应的关键字修饰,那么子类也同样有关键字修饰。
子类关键字相关概念:
子类或实现类,必须使用 final或 non-sealed 这两个关键字来修饰
final关键字:修饰子类,则该子类不能再被其它类继承
non-sealed关键字:修饰子类,子类还可以再被其它类继承
下面通过代码来说明。
子类Cat类
1.经过final关键字修饰的Cat类,无法再被其他类继承。
2.Cat类要直接继承 密封类Animal 才可以。
3. 如果Cat类先继承某个类如Test,然后Test类再继承Animal类的话,那么idea会报错提示Cannot inherit from final 'com.xch.entity.Cat'。(这种方式即为间接继承)
public final class Cat extends Animal {
}
DuanWeiCat类无法继承Cat类,因为Cat类被 final关键字修饰了
子类Dog类
1.经过non-sealed关键字修饰的Dog类,可以再被其它类继承。
public non-sealed class Dog extends Animal{
}
密封类的优点:
1.更加的安全,可以限制继承的子类,避免不必要的继承
2.更加的可控,密封类限制了继承范围,只允许在 同一个包 下的类才可以继承,这
减少了代码的复杂性。
这个特性目前我自己还没在工作中用到,但是既然有这个特性出来,那必然有它的用处。
后续的工作中应该也会用到这块功能的地方。
5.jdk有自己的lombok功能(Record类)
什么是Record类呢?
其实所谓的Record类,就是我们平时创建class类时,把class关键字替换成record关键字。简单一句话描述 Record类,类似于 lombok的属性只读对象。
Record类会帮助我们生成 全参的构造方法 和 获取属性方法 (具体后面可以看字节码文件中的内容)
如下代码,Record类声明属性是在 ()中去声明的(就像是在方法中声明参数一样),而不是在{}中去声明属性。
Record类的使用场景:比较简单的一些javaBean,例如坐标轴的 x轴 和 y轴
public record UserRecord(Long userId, String userName) {
// private Long userId;
// private String userName;
}
Record类的使用如下:
public static void main(String[] args) {
//Record类型的类只提供了 有参构造方法,必须设置全部的属性
UserRecord userRecord = new UserRecord(1L, "xch");
Long userId = userRecord.userId();
String userName = userRecord.userName();
System.out.println(userId);
System.out.println(userName);
UserRecord userRecord2 = new UserRecord(1L, "xch");
UserRecord userRecord3 = new UserRecord(2L, "xch");
// Record类的 equals 会比较两个对象的 属性值 是否相同,Record类的equals会对Object类的equals进行重写(包括hashCode方法,toString方法)
// 如果 属性值 都相同 , true,
// 如果 属性值有一个不同, false,
System.out.println(userRecord.equals(userRecord2));
System.out.println(userRecord.equals(userRecord3));
}
查看Record类的字节码文件,可以看到Record类已经帮我们生成了 全参的构造方法 和 获取属性的方法
6.优化空指针异常信息
在我们平时工作时,在服务器中抛出空指针异常的时候,如果程序相对复杂点的话,那是不是排查起来就比较麻烦,你要判断是这里出现空指针异常,还是那里出现空指针异常了,无形中增加了我们的工作量,工作效率也比较低。
现在JDK17中出现了这样的一个新特性,可以快速帮助我们定位出现空指针异常的位置。
下面通过简单的代码加以说明。
如下,定义了一个实体类NullPointer,类中有个属性为name,然后将nullPointer对象赋为null,那么这时候去get属性的值时,就会报空指针异常。
public static void main(String[] args) {
NullPointer nullPointer = new NullPointer();
nullPointer.setName("nullPointer");
nullPointer = null;
String name = nullPointer.getName();
System.out.println(name);
}
报错内容如下:
Exception in thread "main" java.lang.NullPointerException:
Cannot invoke "com.xch.entity.NullPointer.getName()" because "nullPointer" is null
at com.xch.Test.NullPointerTest.main(NullPointerTest.java:20)Process finished with exit code 1
注意看,Cannot invoke "com.xch.entity.NullPointer.getName()" because "nullPointer" is null 这行是jdk17中新的报错内容,提示说nullPointer这个对象为null,那我们就可以明确的知道出现空指针的位置是在nullPointer这个对象这里。
当然我这里只是简单举例只写了.getName()这么一层,如果你的代码是类似于多层的get的话,如school.getStudent().getClass().getName(),这种情况下如果抛出空指针异常,我们借助jdk17的这一新特性,那是不是就可以直接从报错内容中定位空指针异常是发生在school这个对象这里呢,还是在.getStudent()这里呢,还是在.getClass()这里呢等,那这样就大大节省了我们的排查时间,提高了我们的工作效率。这个新特性我觉得对我们的工作帮助是非常大的。
7.ZGC垃圾收集器(垃圾回收不卡顿,添加虚拟机参数
-XX:+UseZGC)
ZGC在jdk11中就已经诞生了,在jdk15中就已经转正了,然后在jdk17中就已经是非常的成熟的垃圾收集器了,而它的好处就是 程序在进行垃圾回收的时候,不卡顿。
我们在学习JVM的时候,就会遇到一个STW,即Stop World,世界都停止了。就是说在触发垃圾回收的时候,JVM的内存将会被冻结,所有的线程都停止运行,发生卡顿。而STW是在垃圾回收的时候触发的,这种情况是不可避免的,所以我们所能做的就是尽量减少停顿的时间。而ZGC的STW的时间是小于10ms的,几乎是感受不到的。
同时ZGC还有一种优化性能的手段,就是它可以将堆内存设置的很大,可以设置到十几T级别大小,那么就可以大大减少ZGC的次数,从而提高应用的性能。
至此,JDK17的新特性大概有以上几种,下一篇文章将会分享一下我自己在安装多个jdk时如何切花不同jdk版本的方法及遇到的问题。
网址在这:JDK版本切换及注意事项-CSDN博客