OpenJDK中同时会有好几个项目在进行中。这些项目所带来的修改可能会加入到以后版本的JDK中。本文带你看一下OpenJDK中正在进行的重要项目,提前了解以后版本的JDK中会增加的功能。
Amber
Amber项目关注的是Java语言的小改动。Amber项目的一些成果已经被添加到JDK中。这其中包括:Java 10中的局部变量类型推断,也就是对var的使用。
Java 11中的允许在Lambda表达式的形式参数声明中使用var。
Java 12中的switch表达式。
Amber项目中正在探索的Java语言的改动包括模式匹配、数据类和密封类型和对序列化机制的改进。
模式匹配
熟悉Scala的人对模式匹配应该不陌生。Scala的模式匹配与case class一块使用,可以很简洁的把类型检查和数据提取结合起来。Java也在探索类似的功能。模式匹配由两个部分组成:表示匹配条件的断言(predicate)和匹配满足时提取数据的变量。
最简单的模式匹配规则是类型模式。在下面的代码中,如果x是Integer类型,则模式匹配成功,i是提取之后的Integer类型的对象。
if (x instanceof Integer i) {
// i是Integer类型}
更复杂的匹配模式是检查类型之后提取数据。在下面的代码中,如果n的类型是IntNode,那么IntNode中的字段i可以被提取出来,直接使用。
int eval(Node n) {
return switch(n) {
case IntNode(int i) -> i;
case NegNode(Node n) -> -eval(n);
case AddNode(Node left, Node right) -> eval(left) + eval(right);
case MulNode(Node left, Node right) -> eval(left) * eval(right);
};
}
数据类和密封类型
熟悉Kotlin的人看到数据类(data class)和密封类型(sealed type)肯定格外亲切。没有错,Java未来也会有数据类和密封类型。不过Java中的说法是记录类型,用关键字record表示。在下面的代码中,Expr是一个密封类型,ConstantExpr等是数据类。
sealed interface Expr { }
record ConstantExpr(int i) implements Expr { }
record PlusExpr(Expr a, Expr b) implements Expr { }
record TimesExpr(Expr a, Expr b) implements Expr { }
record NegExpr(Expr e) implements Expr { }
数据类有默认的equals、hashCode和toString实现。 使用sealed声明的类型限制了允许的子类型。
序列化机制
Java 自带的序列化机制是一个很糟糕的实现,以至于Java社区都选择忘记了这个功能的存在,而转而使用JSON、XML和ProtoBuf这样的外部序列化机制。Java的序列化机制会要进行修改,主要基于两个原则:需要进行序列化的是数据而不是对象。
序列化实现应该是对象模型的一部分。可以序列化的类应该清楚的定义自己的序列化和反序列化的行为。
下面代码中的Range类通过@Serializer和@Deserializer注解声明了序列化和反序列化的行为。Java运行时就可以知道两个int类型的字段hi和lo是序列化形式中的内容。
public class Range {
int lo;
int hi;
private Range(int lo, int hi) {
if (lo > hi)
throw new IllegalArgumentException(String.format("(%d,%d)", lo, hi));
this.lo = lo;
this.hi = hi;
}
@Serializer
public pattern Range(int lo, int hi) {
lo = this.lo;
hi = this.hi;
}
@Deserializer
public static Range make(int lo, int hi) {
return new Range(lo, hi);
}
}
Valhalla
与Amber项目对应的Valhalla项目中包含的是Java虚拟机上所做的探索。Valhalla项目中正在进行的探索主要是两个:内联类型和特殊化泛型。
内联类型
Java中区分了基本类型和引用类型。基本类型是int和long这样的,而引用类型都继承自Object。内联类型(inline type)给开发者的感觉是写起来像类,工作起来像int这样基本类型。 一个典型的例子是表示二维坐标的Point类。按照通常的设计,Point类有两个int字段分别表示X和Y轴的坐标。每个Point对象有自己的标识,并在Java堆上分配空间。Point对象所占用的内存空间大于两个int基本类型的空间。 这样的空间浪费在处理成百上千个Point对象时,就变得很可观。
在开发时,内联类型可像普通的类型一样工作;在运行时,Java虚拟机会用更有效的方式来存储。下面代码中的inline class用来声明内联类型。
public inline class Point {
int x;
int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
}
泛型特殊化
泛型特殊化(Generic Specialization)是一个很有趣的话题。Java泛型目前的实现机制是类型擦除(type erasure)。类似List和List这样的类型,在运行时都只有List这一种表示形式。List在运行时是不可具体化的。泛型的一个限制是只能使用引用类型,也就是Object及其子类型。基本类型是不能使用的。所以List是错误的,只能使用对应的封箱类型,如List。只所以不能使用基本类型,是因为Java虚拟机对引用类型和基本类型操作的字节代码指令不同。
随着内联类型的引入,Java泛型可以扩展到支持基本类型。不过,这并不意味着完整的泛型特殊化实现。List仍然是不可具体化的。
Loom
Loom是本人最感兴趣的一个项目。Loom项目有3个主题,分别是计算续体(Continuation)、纤程(Fiber)和尾调用消除(tail-call elimination)。
计算续体是一段可以被暂停执行的代码。Java中的Continuation类使用Runnable表示需要执行的代码。在Runnable的实现中可以通过Continuation.yield()方法来暂停自己。每次调用Continuation对象的run(),所对应的代码要么运行到结束,要么运行到下一个yield()方法调用点。
在下面的代码中,Continuation对象cont的第一次执行会输出before,然后暂停;第二次执行会输出after,然后终止。
Continuation cont = new Continuation(SCOPE, () -> {
System.out.println("before");
Continuation.yield(SCOPE);
System.out.println("after");
});
while (!cont.isDone()) {
cont.run();
}
计算续体是低层次的结构,一般只供Java库的开发人员使用。一般的开发人员在日常编程中使用纤程。纤程可以看成是轻量级的线程。与线程相比,纤程的运行时额外开销更小,切换速度更快。Java应用中可以同时创建的纤程数量远远大于线程的数量。纤程是计算续体和调度器的整合。纤程可以与结构化并发性(Structured Concurrency)的思想结合起来,实现简洁易懂的并发代码。
尾调用消除可以通过复用函数栈来降低内存开销。这也是Loom项目的目标之一。
本文只是简单的介绍了Amber、Valhalla和Loom这3个OpenJDK的主要项目。除了这3个项目之外,还有其他比较小的项目也在进行中。关于这3个项目的细节,以及更多Java语言和Java平台的内容,请参与我在GitChat上的分享,只需要6元。