Java 14 特性简介

java 14出来已经有一段时间了,相比之前的小幅更新,这一次的新增语法特性还是比较多的。而且前面的众多特性,包括14中的部分特性都是为模式匹配做准备,基本上就差临门一脚了。本文将仅对新增语法特性做一个简单介绍。

305: Pattern Matching for instanceof (Preview)

这是一个preview特性,需要编译的时候加上--enable-preview参数,同时还要指定--release或者--source,仅作尝鲜使用。

这个特性本身的作用是简化instance of的使用,因为如果instance of为真,那么之后的强制转型就显得很多余。比如:

1
2
3
4
if (obj instanceof String) {
String s = (String) obj;
// use s
}

现在可以写成:

1
2
3
4
5
if (obj instanceof String s) {
// can use s here
} else {
// can't use s here
}

注意到,新的语法instance of类型之后多了一个变量,这也是唯一的区别。虽然他看上去仍然去有点多余,为什么不在对应的作用域内obj的类型直接就当成String呢。我猜测是这个特性为了和后续的模式匹配的解构风格保持一致,毕竟这个特性本身就是为模式匹配铺路的。

359: Records (Preview)

这依然是一个preview特性,它很像c的结构体,但准确来讲它更像scala的case class。加上JEP 360: Sealed Classes (Preview)之后,就等于支持代数数据类型了,当然目前也差不多可以用了,差别是模式匹配的时候编译器能否判断代码有没有把所有的可能性写全。抛开这些,Record可以视为一个语法糖,因为他编译之后可以还原成一个等价的class。利用这个特性,我们可以少写很多代码,因为record可以自动生成:构造器,get方法,toStringhashCodeequals方法,注意:这里并没有set方法,因为record默认是不可变的。而且record可以实现接口,但是不能继承其他类,同时除此以外和一个普通的class没有什么差别。

1
record Point(int x, int y) { }

语法如上,因为record还能添加自定义构造器和实现方法,为了保持一致还是保留了大括号(虽然看上去很多余).

JEP 361: Switch Expressions (Standard)

这个之前就有,不过在java 14中不再是preview特性了,简单将,switch语句是一个表达式,它是有值的.当然语法和switch statement有一些差别.

1
2
3
4
5
6
switch (day) {
case MONDAY, FRIDAY, SUNDAY -> System.out.println(6);
case TUESDAY -> System.out.println(7);
case THURSDAY, SATURDAY -> System.out.println(8);
case WEDNESDAY -> System.out.println(9);
}

这段代码并没有体现出switch expressions是一个表达式,与switch statement的最大差别是他没有fall through特性,不需要到处加break了.

王垠曾经讲:访问者模式只是模式匹配丑陋的模仿(大意).观点正确与否暂且不论,模式匹配和观察者模式的确有很多共同之处.比如一个访问者接口的定义(以遍历二叉树为例):

1
2
3
4
public interface Visitor {
public void visit(Node n);
public void visit(Leaf l);
}

haskell的模式匹配定义如出一辙

1
2
3
visit :: Tree e -> ()
visit (Node n) = ...
visit (Leaf f) = ...

虽然这些语法离完整的模式匹配还差一脚,不过已经差不多够了,接下来就用新的语法写一个简单的计算器.

计算器

首先是token的定义。scala中有与case class对应的cass object,也就是单例的record。不过java目前还没有,用enum代替:

1
2
3
4
5
6
7
8
9
10
11
interface Token { }

record Number(int i) implements Token{ }

enum Operator implements Token{
ADD,MINUS,MULTIPLY,DIVIDED
}

enum Bracket implements Token{
LEFT,RIGHT;
}

由于java标准库中没有tuple,再加一个tuple,用来当多返回值,同时返回token和所在的位置.

1
record Tuple<A,B>(A a,B b){}

然后是parser部分,省去了tokenizer

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
static Optional<List<Token>> parseAll(String s) {
var i = 0;
var list = new LinkedList<Token>();
while (i < s.length()) {
var r = parse(s, i);
if (r.isPresent()) {
list.add(r.get().a());
i = r.get().b();
} else {
return Optional.empty();
}
}
return Optional.of(list);
}

static Optional<Tuple<Token, Integer>> parse(String s, int p) {
var c = s.charAt(p);
return switch (c) {
case '(' -> Optional.of(new Tuple<>(Bracket.LEFT, p + 1));
case ')' -> Optional.of(new Tuple<>(Bracket.RIGHT, p + 1));
case '*' -> Optional.of(new Tuple<>(Operator.MULTIPLY, p + 1));
case '/' -> Optional.of(new Tuple<>(Operator.DIVIDED, p + 1));
case '+' -> Optional.of(new Tuple<>(Operator.ADD, p + 1));
case '-' -> Optional.of(new Tuple<>(Operator.MINUS, p + 1));
default -> parseNumber(s, p);
};
}

static Optional<Tuple<Token, Integer>> parseNumber(String s, int p) {
int i = 0;
if (!Character.isDigit(s.charAt(p))){
return Optional.empty();
}

while (p < s.length() && Character.isDigit(s.charAt(p))) {
i = i * 10 + (s.charAt(p++) - '0');
}
return Optional.of(new Tuple<>(new Number(i), p));
}

接下来是解释部分,左递归消除就不赘述了,简单说下文法:

1
2
3
4
5
expr = term expr1
expr1 = (+/-) term expr1 | ε
term = digit | term1
term1 = (*//) digit term1 | ε
digit = 整数 | '(' expr ')'
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
static Optional<Tuple<Double,Integer>> expr(List<Token> list,int p){
var term = term(list, p);
return term.flatMap(x ->{
var tuples = expr1(list, x.b());
return tuples.map(xs -> {
double s = x.a();
for (var tuple : xs.a()) {
switch (tuple.a()) {
case ADD -> s += tuple.b();
case MINUS -> s -= tuple.b();
default -> throw new UnsupportedOperationException();
}
}
return new Tuple<>(s, xs.b());
});
});
}

static Optional<Tuple<List<Tuple<Operator, Double>>, Integer>> expr1(List<Token> list, int p){
var result = new LinkedList<Tuple<Operator,Double>>();
while (p < list.size()) {
var token = list.get(p);
var term = term(list, p + 1);
if(term.isPresent() && (token == Operator.ADD || token == Operator.MINUS)){
result.add(new Tuple<>((Operator)token, term.get().a()));
p = p + 2;
}else {
break;
}
}
return Optional.of(new Tuple<>(result, p));
}

static Optional<Tuple<Double,Integer>> term(List<Token> list,int p){
var digit = digit(list, p);
return digit.flatMap(x -> {
var tuples = term1(list, x.b());
return tuples.map(xs -> {
double s = x.a();
for (Tuple<Operator, Double> tuple : xs.a()) {
switch (tuple.a()) {
case MULTIPLY -> s *= tuple.b();
case DIVIDED -> s /= tuple.b();
default -> throw new UnsupportedOperationException();
}
}
return new Tuple<>(s,xs.b());
});
});
}

static Optional<Tuple<List<Tuple<Operator,Double>>,Integer>> term1(List<Token> list,int p){
var result = new LinkedList<Tuple<Operator,Double>>();
while (p < list.size()) {
var token = list.get(p);
var term = digit(list, p + 1);
if(term.isPresent() && (token == Operator.MULTIPLY || token == Operator.DIVIDED)){
result.add(new Tuple<>((Operator)token, term.get().a()));
p = p + 2;
}else {
break;
}
}
return Optional.of(new Tuple<>(result, p));
}

static Optional<Tuple<Double, Integer>> digit(List<Token> list, int p) {
if (p < list.size()){
var t = list.get(p);
if (t instanceof Number)
return Optional.of(new Tuple<>(((double) ((Number) list.get(p)).i()), p + 1));
else if(t == Bracket.LEFT){
return expr(list, p + 1).flatMap(x -> {
if (x.b() < list.size() && list.get(x.b()) == Bracket.RIGHT) {
return Optional.of(new Tuple<>(x.a(), x.b() + 1));
}else {
return Optional.empty();
}
});
}
}
return Optional.empty();
}

最后返回的是值和字符所在位置的tuple,再加一个包装函数就完工了:

1
2
3
4
5
static Optional<Double> caculate(List<Token> tokens) {
var list = new ArrayList<Token>(tokens.size());
list.addAll(tokens);
return expr(list,0).map(Tuple::a);
}

完整代码在这里