Java 9 到 17 的语言特性更新

此外,Oracle 官方也有清单可供一览: Java Language Changes (opens new window)

当 Java 8 引入流和 Lambda 这两个重大更新时,函数式编程风格赋予了 Java 更少模板代码的语法。虽然最近的版本更新没添加这么富有影响的特性,但带来了很多较小的改进。自从 Java 切换到一个更快的发布节奏后,每六个月就会发布一个新版本。记录类可能是最近更新中最重要的一个特性,模式匹配和封闭类也会让处理纯数据更容易。

Java 17(LTS)

  • switch 模式匹配(预览特性:mag:)

Java 16

Java 15

  • 包含有用信息的空指针异常

Java 14

Java 11(LTS)

Java 9

  • 匿名内部类的钻石操作符
  • try-with-resources 语句中允许使用 effectively-final 变量
  • 下划线不再是合法变量名

想要一览塑造这个新平台所有的 JEP,其涵盖了包括 API 、性能与安全方面的改进,参看这份精选清单:Java 8 以来所有的改进 (opens new window) 

开始支持版本: JDK 17 (opens new window) ( JDK 15 (opens new window) JDK 16 (opens new window) 为预览特性)

封闭类用于限定哪些类或接口可以被用于继承或实现它们。这给设计公共 API 和替换枚举来构建固定数量的可选项,提供了一个更好的工具。

老版本的 Java 也提供了一些机制来实现类似的效果。标记为 final 的类不允许被继承,配合访问修饰符就能确保仅同一包中的类才能继承。

在此之上, 封闭类 提供了更细粒度的控制,让开发者能显式地列举其子类。

public sealed class Shape
    permits Circle, Quadrilateral {...}

1

2

在这个例子中,被允许继承 Shape 类的只有 Circle 和 Quadrilateral 类。实际上, permits这个关键字有些歧义,因为它不止有允许的含义,其 要求列举的类直接继承封闭类 。

此外,正如人们所期望的那样,如果 任何其它的类试图继承这个封闭类,都会出现编译错误

继承封闭类的类需要符合一些规则。

开发者被强制每次都需要显式定义出封闭类继承的边界,通过添加任意一个下面修饰符到被允许的子类上来实现:

final
sealed
non-sealed

因为子类本身也可以是封闭的,这就意味着可以定义 整条继承链包含限定的可选项 :

public sealed class Shape
    permits Circle, Quadrilateral, WeirdShape {...}

public final class Circle extends Shape {...}

public sealed class Quadrilateral extends Shape
    permits Rectangle, Parallelogram {...}
public final class Rectangle extends Quadrilateral {...}
public final class Parallelogram extends Quadrilateral {...}

public non-sealed class WeirdShape extends Shape {...}

1

2

3

4

5

6

7

8

9

10

11

如果这些类比较简短,且大多仅和数据相关,那么可以将它们声明在 同一个源文件中, permits 关键字也可以忽略

public sealed class Shape {
  public final class Circle extends Shape {}

  public sealed class Quadrilateral extends Shape {
    public final class Rectangle extends Quadrilateral {}
    public final class Parallelogram extends Quadrilateral {}
  }

  public non-sealed class WeirdShape extends Shape {}
}

1

2

3

4

5

6

7

8

9

10

记录类也可以作为封闭类的子类,因为它们是隐式 final 的。

被允许继承的类必须和父类(封闭类)在同一个包里,如果是使用 java 模块,那它们必须在同一模块中。

:warning:技巧:考虑使用封闭类优于枚举

封闭类 出现前,只能用 枚举类 对固定可选项建模,比如:

enum Expression {
  ADDITION,
  SUBTRACTION,
  MULTIPLICATION,
  DIVISION
}

1

2

3

4

5

6

然而,所有的变量都要在同一个源文件中,且 枚举类 不支持需要实例的情况(而不是常量),例如表示一个类型的单个消息。

封闭类 提供一个比 枚举类 更好的选择,使得用普通类来为固定可选项建模成为可能。当 switch 模式匹配 在生产环境可用时就更能充分发挥其作用, 封闭类 能像枚举一样在 switch 表达式中使用,编译器能自动检查代码是否涵盖了全部情况。

枚举类的值可以使用 values 方法列举出来。对应到封闭类和封闭接口,可以使用 getPermittedSubclasses 方法例举出所有被允许继承的子类。

switch 模式匹配(预览特性:mag:)

开始支持版本: JDK 17 (opens new window) 为预览特性

此前, switch 表达式的用法十分局限:条件仅仅支持完全相等的情况,而且只支持很少几类类型:数值、枚举类和字符串。

这个预览特性增强了 swith 表达式的用法, 可以用在任意的类型上,匹配更复杂的模式 。

这些新特性是 向后兼容的 , switch 搭配传统的常量就如以往一样的使用,例如和枚举值:

var symbol = switch (expression) {
  case ADDITION       -> "+";
  case SUBTRACTION    -> "-";
  case MULTIPLICATION -> "*";
  case DIVISION       -> "/";
};

1

2

3

4

5

6

然而,随着 JEP 394: Pattern Matching for instanceof (opens new window) 的引入,现在可以和类型模式搭配使用:

return switch (expression) {
  case Addition expr       -> "+";
  case Subtraction expr    -> "-";
  case Multiplication expr -> "*";
  case Division expr       -> "/";
};

1

2

3

4

5

6

模式还支持 卫语句 ,写法为 type pattern && guard expression :

String formatted = switch (o) {
    case Integer i && i > 10 -> String.format("a large Integer %d", i);
    case Integer i           -> String.format("a small Integer %d", i);
    default                  -> "something else";
};

1

2

3

4

5

这和使用 if 声明的类型模式构成了很好的对称性,因为类似的模式可以用于条件语句:

if (o instanceof Integer i && i > 10) {
  return String.format("a large Integer %d", i);
} else if (o instanceof Integer i) {
  return String.format("a large Integer %d", i);
} else {
  return "something else";
}

1

2

3

4

5

6

7

if 条件类似, 模式变量的作用域是分支敏感的 (flow sensitive)。比如,在下面的条件中,变量 i 的作用域为卫语句及其右边的表达式。

case Integer i && i > 10 -> String.format("a large Integer %d", i);

1

总体来说,模式匹配会按你期待的那样工作,但其中涉及了很多规则和边缘情况。如果你感兴趣,我推荐你读下相关的 JEPs 或是看下Pattern matching for instanceof章节。

Switch 现在也能匹配 null 值 。通常来说,当 null 值传给 switch 会报 NullPointerException 。当一个常量试图匹配 null 的时候也会出现这种情况。然而,现在可以显示得声明 null 在分支上:

switch (s) {
  case null  -> System.out.println("Null");
  case "Foo" -> System.out.println("Foo");
  default    -> System.out.println("Something else");
}

1

2

3

4

5

switch 表达式没有完全覆盖各种情况分支,或是一个分支条件完全包含了另一个分支,编译器会报错。

Object o = 1234;

// OK
String formatted = switch (o) {
    case Integer i && i > 10 -> String.format("a large Integer %d", i);
    case Integer i           -> String.format("a small Integer %d", i);
    default                  -> "something else";
};

// 编译错误 - 'switch' 表达式没有涵盖所有可能的输入值
String formatted = switch (o) {
    case Integer i && i > 10 -> String.format("a large Integer %d", i);
    case Integer i           -> String.format("a small Integer %d", i);
};

// 编译错误 - 第二个条件已包含在第一个条件分支中
String formatted = switch (o) {
    case Integer i           -> String.format("a small Integer %d", i);
    case Integer i && i > 10 -> String.format("a large Integer %d", i);
    default                  -> "something else";
};

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

这个 预览 特性需要通过 --enable-preview 标记来显式开启。当然我们试目以待吧,因为更多的特性将要到来: JEP405 (opens new window) 针对 Java 18 ,旨在带来可用于解构的数组模式和记录类模式。

开始支持版本: JDK 16 (opens new window) ( JDK 14 (opens new window) JDK 15 (opens new window) 为预览特性)

<
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值