Java14中的好用特性分享(1)
Java 新的版本发布规划的结果就是Java更新似乎快得让人眼花缭乱,之前Java小版本更新没人在乎,现在没有了小版本,即使是非长期支持版发布也会惊起一滩鸥鹭。Java14就是非长期支持版,下一版LTS要等到2021年9月份的Java17。不过对于Java9以后就不熟悉的开发者来说,目前的jdk提供的很多新语法和新API的确非常诱人。这里我选择了一些Java14中的好用的特性简单说一下。
Java 目前的发布计划有点像Ubuntu,每年3月和9月都会出一版。但是多数是非长期支持版(non-LTS),长期支持版(LTS)要三年才发一次。第一个LTS是2018年9月的Java11(所以Java9和10也是nlts),所以下一版就是2021年了。
Java 14虽然提供了很多新特性(大家可以随便百度一下,比如《Java14发布,16大新特性,代码更加简洁明快》号称16大新特性,一般认为最少有12大新特性),但是对于我哦们平常开发用到的不多,主要有5条:
- switch 表达式
- instanceof的模式匹配
- 更有用的空指针异常信息
- record 类型
- 文本块
而且更要命的是,这里面只有两条是标准功能:switch表达式和空指针异常。其余都是预览功能,也就是后面的版本不算可能转正也可能移除。不过这么好用的功能不转正没有天理啊!
更有用的空指针异常信息
先介绍这个吧,我觉得是最不可代替的功能。其他功能我们都可以通过自己编码解决,这个只能靠JDK提供。
以前的Java在报空指针的时候只会给出行号,但是行内到底是哪个变量空指针了依然让人一览懵X。比如下面的代码:
public class Obj {
private Obj2 o2;
}
public class Obj2 {
private String name;
}
public static void main(String[] args) {
Obj o = new Obj();
System.out.println(o.getO2().getName());
}
异常信息是
Exception in thread "main" java.lang.NullPointerException
at info.manxi.Main.main(Main.java:15)
那么到底是o是空指针还是o2是空指针(当然这里是明确的),还是println方法不能接受null参数(小白可能不知道),异常并没有指明。
Java14中提供了更有用的异常信息,不过这个功能默认是关闭的,需要通过-XX:+ShowCodeDetailsInExceptionMessages参数打开:

再次运行,异常信息变为
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "info.manxi.Obj2.getName()" because the return value of "info.manxi.Obj.getO2()" is null
at info.manxi.Main.main(Main.java:15)
明确指出来o2是空。
更好用的switch表达式
Java中的switch语法经过了多次优化。大的来讲分两类,之前叫switch语句,现在叫switch表达式。语句和表达式的区别是语句可以赋值给一个变量,整体是表达式。
最早的switch语句只能匹配4种基本数据类型和枚举,后来通过语法糖实现了可以匹配String类型。Java12开始预览提供赋值功能。
假设我们需要通过case判断来确定一个外部变量的新值,我们可能需要这样做:
public static void main(String[] args) {
int a = Integer.parseInt(args[0]);
String b = "";
switch (a) {
case 0: {
b = "0";
break;
}
case 1: {
b = "12";
break;
}
case 2: {
b = "987";
break;
}
default:{
}
}
System.out.println(b);
}
上面这段代码在IDEA中会得到提示:使用增强版switch
public static void main(String[] args) {
int a = Integer.parseInt(args[0]);
String b = "";
switch (a) {
case 0 -> b = "0";
case 1 -> b = "12";
case 2 -> b = "987";
default -> {
}
}
System.out.println(b);
}
IDEA 帮我们用->代替了冒号,然后去掉了break,因为即使没有break也不会再进入下一个case块了。
进一步,由于default没有处理b,我们把b的初始化值放在default块,这样可以把整个switch块复制给b(末尾加分号):
public static void main(String[] args) {
int a = Integer.parseInt(args[0]);
String b =
switch (a) {
case 0 -> b = "0";
case 1 -> b = "12";
case 2 -> b = "987";
default -> b = "";
};
System.out.println(b);
}
实际上,case中的b = 根本没用,直接返回字符串就可以,返回一个赋值操作看起来不伦不类:
public static void main(String[] args) {
int a = Integer.parseInt(args[0]);
String b = switch (a) {
case 0 -> "0";
case 1 -> "12";
case 2 -> "987";
default -> "";
};
System.out.println(b);
}
看起来似乎不错,不过到现在其实并没有接触到switch表达式的核心:switch表达式的核心是yield。可能有人熟悉其他语言(比如python)见过yield关键字,不过yield和java9引入模块后的exports、modules等一样,并非保留字,可以用做变量名:
public static void main(String[] args) {
int a = Integer.parseInt(args[0]);
int yield = Integer.parseInt(args[1]);
String b = switch (a) {
case 0 -> "0";
case 1 -> "12";
case 2 -> {
if (yield > 0) {
yield "---";
}
yield "32333";
}
default -> "";
};
System.out.println(b);
}
yield的作用就是case中的return,用于在switch表达式中根据条件或者其他逻辑判断等生成返回值。
习惯老写法的我们,可以通过生成一个新方法和return来实现同样的功能
使用switch表达式,必须把所有可能情况都覆盖完,不然编译不了。通常会使用default
最后说一下多case合并匹配。以前使用switch语句的时候,因为没有break会直接进入下个case块,所以多case合并匹配同一个结果的时候只要把几个case并列起来就行。使用switch表达式就不行了,如果要合并,就连case也去掉,只留第一个case:
public static void main(String[] args) {
int a = Integer.parseInt(args[0]);
int yield = Integer.parseInt(args[1]);
String b = switch (a) {
case 0, 1, 2 -> {
if (yield > 0) {
yield "---";
}
yield "32333";
}
default -> "";
};
System.out.println(b);
}
