前言
Java最近几年的更新有点快,自从 Java 8 发布了 Lambda 和 Stream 之后,Java 像打了鸡血一样,半年一个版本的发布,生产队的驴也没这么勤快,导致很多新特性难以掌握使用,下面我们来简单的聊一聊。
一、Java 9(2017年9月)
1、接口里可以添加私有接口
JAVA 8 对接口增加了默认方法的支持,在 JAVA 9 中对该功能又来了一次升级,现在可以在接口里定义私有方法,然后在默认方法里调用接口的私有方法。
这样一来,既可以重用私有方法里的代码,又可以不公开代码
public interface TestInterface {
default void wrapMethod(){
innerMethod();
}
private void innerMethod(){
System.out.println("");
}
}
2、匿名内部类也支持钻石(diamond)运算符
JAVA 5 就引入了泛型(generic),到了 JAVA 7 开始支持钻石(diamond)运算符:<>
,可以自动推断泛型的类型:
List<Integer> numbers = new ArrayList<>();
但是这个自动推断类型的钻石运算符可不支持匿名内部类,在 JAVA 9 中也对匿名内部类做了支持:
List<Integer> numbers = new ArrayList<>() {
...
}
3、增强的 try-with-resources
Java 7 中增加了try-with-resources
的支持,可以自动关闭资源:
try (BufferedReader bufferReader = new BufferedReader(...)) {
return bufferReader.readLine();
}
但需要声明多个资源变量时,需要在 try 中写多个变量的创建过程:
try (BufferedReader bufferReader0 = new BufferedReader(...);
BufferedReader bufferReader1 = new BufferedReader(...)) {
return bufferReader0.readLine();
}
Java9 中对这个功能进行了增强,可以引用 try 代码块之外的变量来自动关闭:
BufferedReader bufferReader0 = new BufferedReader(...);
BufferedReader bufferReader1 = new BufferedReader(...);
try (bufferReader0; bufferReader1) {
System.out.println(br1.readLine() + br2.readLine());
}
二、Java10(2018年3月)
1、局部变量的自动类型推断(var)
JAVA 10 带来了一个很有意思的语法 - var
,它可以自动推断局部变量的类型,以后再也不用写类型了,也不用靠 lombok 的 var
注解增强了
var message = "Hello, Java 10";
不过这个只是语法糖,编译后变量还是有类型的,使用时还是考虑下可维护性的问题,不然写多了可就成 JavaScript 风格了
三、Java11(2018年9月)
1、Lambda 中的自动类型推断(var)
JAVA 11 中对 Lambda 语法也支持了 var
这个自动类型推断的变量,通过 var 变量还可以增加额外的注解:
List<String> languages = Arrays.asList("Java", "Groovy");
String language = sampleList.stream()
.map((@Nonnull var x) -> x.toUpperCase())
.collect(Collectors.joining(", "));
assertThat(language).isEqualTo("Java, Groovy");
2、javac + java 命令一把梭
以前编译一个 java 文件时,需要先 javac 编译为 class,然后再用 java 执行,现在可以一把梭了:
$ java HelloWorld.java
Hello Java 11!
3、Java Flight Recorder 登陆 OpenJDK
Java Flight Recorder 是个灰常好用的调试诊断工具,不过之前是在 Oracle JDK 中,也跟着 JDK 11 开源了,OpenJDK 这下也可以用这个功能
四、Java2(2019年3月)
1、更简洁的 switch 语法
在之前的 JAVA 版本中,switch
语法还是比较啰嗦的,如果多个值走一个逻辑需要写多个 case
:
DayOfWeek dayOfWeek = LocalDate.now().getDayOfWeek();
String typeOfDay = "";
switch (dayOfWeek) {
case MONDAY:
case TUESDAY:
case WEDNESDAY:
case THURSDAY:
case FRIDAY:
typeOfDay = "Working Day";
break;
case SATURDAY:
case SUNDAY:
typeOfDay = "Day Off";
}
到了 JAVA 12,这个事情就变得很简单了,几行搞定,而且!还支持返回值:
typeOfDay = switch (dayOfWeek) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> "Working Day";
case SATURDAY, SUNDAY -> "Day Off";
};
2、instanceof + 类型强转一步到位
之前处理动态类型碰上要强转时,需要先 instanceof
判断一下,然后再强转为该类型处理:
Object obj = "Hello Java 12!";
if (obj instanceof String) {
String s = (String) obj;
int length = s.length();
}
现在 instanceof
支持直接类型转换了,不需要再来一次额外的强转:
Object obj = "Hello Java 12!";
if (obj instanceof String str) {
int length = str.length();
}
五、Java13(2019年9月)
1、switch 语法再增强
JAVA 12 中虽然增强了 swtich
语法,但并不能在 ->
之后写复杂的逻辑,JAVA 12 带来了 swtich
更完美的体验,就像 lambda
一样,可以写逻辑,然后再返回:
typeOfDay = switch (dayOfWeek) {
case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> {
// do sth...
yield "Working Day";
}
case SATURDAY, SUNDAY -> "Day Off";
};
2、文本块(Text Block)的支持
你是否还在为大段带换行符的字符串报文所困扰,换行吧一堆换行符,不换行吧看着又难受:
String json = "{\"id\":\"1697301681936888\",\"nickname\":\"空无\",\"homepage\":\"https://juejin.cn/user/1697301681936888\"}";
Java 13 中帮你解决了这个问题,增加了文本块的支持,现在可以开心的换行拼字符串了,就像用模板一样:
String json = """
{
"id":"1697301681936888",
"nickname":"空无",
"homepage":"https://juejin.cn/user/1697301681936888"
}
""";
六、Java14(2020年3月)
1、新增的 record 类型,干掉复杂的 POJO 类
一般我们创建一个 POJO 类,需要定义属性列表,构造函数,getter/setter,比较麻烦。JAVA 14 为我们带来了一个便捷的创建类的方式 - record
public record UserDTO(String id,String nickname,String homepage) { };
public static void main( String[] args ){
UserDTO user = new UserDTO("1697301681936888","空无","https://juejin.cn/user/1697301681936888");
System.out.println(user.id);
System.out.println(user.nickname);
System.out.println(user.id);
}
IDEA 也早已支持了这个功能,创建类的时候直接就可以选:
不过这个只是一个语法糖,编译后还是一个 Class,和普通的 Class 区别不大
2、更直观的 NullPointerException 提示
NullPointerException 算是 Java 里最常见的一个异常了,但这玩意提示实在不友好,遇到一些长一点的链式表达式时,没办法分辨到底是哪个对象为空。
比如下面这个例子中,到底是 innerMap
为空呢,还是 effected
为空呢?
Map<String,Map<String,Boolean>> wrapMap = new HashMap<>();
wrapMap.put("innerMap",new HashMap<>());
boolean effected = wrapMap.get("innerMap").get("effected");
// StackTrace:
Exception in thread "main" java.lang.NullPointerException
at org.example.App.main(App.java:50)
Java 14 也 get 到了 Javaer 们的痛点,优化了 NullPointerException 的提示,让你不在困惑,一眼就能定位到底“空”在哪!
Exception in thread "main" java.lang.NullPointerException: Cannot invoke "java.lang.Boolean.booleanValue()" because the return value of "java.util.Map.get(Object)" is null
at org.example.App.main(App.java:50)
现在的 StackTrace 就很直观了,直接告诉你 effected
变量为空。
3、安全的堆外内存读写接口,别再玩 Unsafe 的骚操作了
在之前的版本中,Java 如果想操作堆外内存(DirectBuffer),还得 Unsafe 各种 copy/get/offset。现在直接增加了一套安全的堆外内存访问接口,可以轻松的访问堆外内存,再也不用搞 Unsafe 的骚操作了。
// 分配 200B 堆外内存
MemorySegment memorySegment = MemorySegment.allocateNative(200);
// 用 ByteBuffer 分配,然后包装为 MemorySegment
MemorySegment memorySegment = MemorySegment.ofByteBuffer(ByteBuffer.allocateDirect(200));
// MMAP 当然也可以
MemorySegment memorySegment = MemorySegment.mapFromPath(
Path.of("/tmp/memory.txt"), 200, FileChannel.MapMode.READ_WRITE);
// 获取堆外内存地址
MemoryAddress address = MemorySegment.allocateNative(100).baseAddress();
// 组合拳,堆外分配,堆外赋值
long value = 10;
MemoryAddress memoryAddress = MemorySegment.allocateNative(8).baseAddress();
// 获取句柄
VarHandle varHandle = MemoryHandles.varHandle(long.class, ByteOrder.nativeOrder());
varHandle.set(memoryAddress, value);
// 释放就这么简单,想想 DirectByteBuffer 的释放……多奇怪
memorySegment.close();
4、新增的 jpackage 打包工具,直接打包二进制程序,再也不用装 JRE 了
之前如果想构建一个可执行的程序,还需要借助三方工具,将 JRE 一起打包,或者让客户电脑也装一个 JRE 才可以运行我们的 JAVA 程序。现在 JAVA 直接内置了 jpackage
打包工具,帮助你一键打包二进制程序包,终于不用乱折腾了
七、Java 15(2020年9月)
1、ZGC 和 Shenandoah 两款垃圾回收器正式登陆
在 Java 15中,ZGC 和 Shenandoah 再也不是实验功能,正式登陆了(不过 G1 仍然是默认的)。如果你升级到 Java 15 以后的版本,就赶快试试吧,性能更强,延迟更低
2、封闭(Sealed )类
Java 的继承目前只能选择允许继承和不允许继承(final 修饰),现在新增了一个封闭(Sealed )类的特性,可以指定某些类才可以继承:
public sealed interface Service permits Car, Truck {
int getMaxServiceIntervalInMonths();
default int getMaxDistanceBetweenServicesInKilometers() {
return 100000;
}
}
八、Java16(2021年3月)
1、Record记录
记录声明一种数据类,这种类在 ORM 框架中被定义为数据传输对象 (DTO) 或实体。
传统创建类的方法是创建一个具有属性和所有参数构造函数的public类,并定义所有 getter/setter 以及 equals、hashCode 和 toString。这需要大量额外的代码,人们于是使用代码生成库和插件(如 Lombok)来减少这些代码,但仍然很麻烦。
Java 16 引入了记录Record类型来简化此类类的创建:
import java.util.*;
public class RecordTest {
public static void main(String... args) {
Student s1 = new Student(1, "Shazin", 1);
Student s2 = new Student(2, "Shahim", 2);
System.out.println("Student 1 : "+s1);
System.out.println("Student 2 : "+s2);
System.out.println("s1 == s2 : "+(s1 == s2));
System.out.println("s1.equals(s1) : "+(s1.equals(s1)));
System.out.println("s1.equals(s2) : "+(s1.equals(s2)));
record GraduateStudent(Student student, List<String> qualifications) {};
GraduateStudent gs1 = new GraduateStudent(s1, Arrays.asList("A/S", "BSc"));
System.out.println("GraduateStudent 1 : " + gs1);
}
private static record Student(Integer id, String name, Integer grade) {
public Student { // All args constructor
if (grade < 0 || grade > 13) {
throw new IllegalArgumentException("Grade must be between 1 and 13");
}
}
}
}
2、流接口的新方法
流接口中引入了几种新方法,可以用来提高性能和减少样板代码。一个是Stream.toList()方法,当需要将流值聚合到列表时,此方法删除了collect方法的调用。
List<Integer> evenNos = nos.stream().filter(i -> i % 2 == 0).toList(); //.collect(Collectors.toList())
System.out.println("Even numbers : "+evenNos);
此外,还引入了一个新方法Stream.mapMulti(),它是Stream.flatMap()的更重要的实现。它的实际应用有点复杂。
3、模式匹配
模式匹配是对Java16的一个非常有用的补充,它消除了在使用instanceof操作符之后强制转换的需要。
import java.util.*;
public class PatternMatchingTest {
public static void main(String... args) {
String name = "Shazin";
List<Integer> nos = Arrays.asList(1, 2, 3, 4, 5);
Map map = Collections.singletonMap("Key", "Value");
Long bigInt = 1l;
System.out.println("Name Length : " + length(name));
System.out.println("Nos Length : " + length(nos));
System.out.println("Map Length : " + length(map));
System.out.println("bigInt Length : " + length(bigInt));
}
private static int length(Object o) {
if (o instanceof String s) { // s variable of type String
return s.length();
} else if (o instanceof Collection c) { // c variable of type Collection
return c.size();
} else if (o instanceof Map m) { // m variable of type Map
return m.size();
} else { // o variable of type Object
return 0;
}
}
}
此功能允许在使用instanceof运算符之后立即使用本地作用域变量名,该运算符与所使用的类型匹配,并且可以在不强制转换的情况下使用。这消除了许多样板代码。