从 Java 12 到 Java 17 那些激动人心的新特性

2022年01月15日 阅读数:1
这篇文章主要向大家介绍从 Java 12 到 Java 17 那些激动人心的新特性,主要内容包括基础应用、实用技巧、原理机制等方面,希望对大家有所帮助。

点击“终码一辈子”,关注,置顶公众号ide

每日技术干货,第一时间送达!



2021 年 9 月,Oracle 发布了 Java 17,Java 的下一个长期支持版本。若是你在使用 Java 8 或 Java 11,可能不会注意到 Java 12 以后新增的一些很酷的新特性。函数


由于这是一个很重要的版本,我会突出介绍一些我我的很感兴趣的新特性!学习


须要注意的是,Java 中的大多数变动首先须要通过“预览”阶段,也就是说它们被添加到一个版本中,但尚未完成。人们能够尝试使用它们,但不建议将其用在生产环境中。this


这里所列举的全部特性都已正式添加到 Java 中,而且已通过了预览阶段。spa


 1:封印类


在 Java 15 中处于预览阶段并在 Java 17 中成为正式特性的 封印类,提供了一种新的继承规则限定方法。当你在类或接口前面添加 sealed 关键字的同时,也添加了一个容许扩展这个类或实现这个接口的类的清单。例如,若是你定义了一个类:指针


public abstract sealed class Color permits Red, Blue, Yellow


也就是说,只有 Red、Blue 和 Yellow 能够继承这个类,其余类想要继承它都没法经过编译。调试


你也能够不使用 permits 关键字,而后将类定义与类放在相同的文件中,以下所示:code


public abstract sealed class Color {...}... class Red     extends Color {...}... class Blue    extends Color {...}... class Yellow  extends Color {...}


注意,这些子类并非嵌套在封印类中,而是放在类定义以后。这与使用关键字 permit 是同样的效果,能够扩展 Color 的类只有 Red、Blue 和 Yellow。orm


那么,封印类一般用在哪里?经过限定继承规则,同时也限定了封装规则。假设你正在开发一个库,而且须要将抽象类 Color 包含在其中。你知道 Color 这个类以及哪些类须要扩展它,但若是它被声明为 public 的,那么你有什么办法能够阻止外部代码扩展它?对象


若是有人误解了它的用途并用 Square 对它进行了扩展,该怎么办?这符合你的意图吗?或者你实际上是想让 Color 保持私有?但即便是这样,包级别的可见性也不能避免全部问题。若是后来有人对这个库进行了扩展了该怎么办?他们如何可以知道你只打算让一小部分类集成 Color?


封印类不只能够保护你的代码不受外部代码的影响,仍是一种向你可能从未见过的人传达意图的方式。若是一个类是封印的,你是在传达只有某些类能够扩展它。这种健壮性能够确保在多年之后任何阅读你代码的人都会理解代码的严谨。


2:加强的空指针异常


加强的 空指针异常 是一个有趣的更新——不会太复杂,但仍然很受欢迎。这个加强在 Java 14 中正式发布,提升了空指针异常 (NullPointerException,简称 NPE) 的可读性,能够打印出在抛出异常位置所调用的方法的名称和空变量的名称。例如,若是你调用 a.b.getName(),而 b 为空,那么异常的堆栈跟踪信息会告诉你调用 getName() 失败,由于 b 是空的。


咱们都知道,NPE 是一种很是常见的异常,虽然在大多数状况下找出致使抛出异常的根源并不难,但你会时不时地遇到同时有两三个可疑变量的状况。你进入调试模式,开始查看代码,但问题很难重现。你只能试着回忆最初作了什么致使抛出 NPE 的。


若是你能提早得到这些信息,就不用这些麻烦地调试了。这就是这个特性的闪光点:不用再猜想 NPE 是从哪里抛出来的。在关键时刻,当你遇到难以重现的异常场景时,你就有了解决问题所需的一切。


这绝对是个救星!


3:switch 表达式


但愿你耐心听我说几句——switch 表达式(在 Java 12 中预览,并正式添加到 Java 14 中) 是 switch 语句和 lambda 之间的某种结合。真的,当我第一次向别人描述 switch 表达式时,个人说法是他们把 switch 语句 lambda 化了。请看下面这个语法:


String adjacentColor = switch (color) {    case Blue, Green -> "yellow";    case Red, Purple -> "blue";    case Yellow, Orange -> "red";    default             -> "Unknown Color";};


如今明白个人意思了吗?


一个明显的区别是没有了 break 语句。switch 表达式延续了 Oracle 让 Java 语法更简洁的趋势。Oracle 很是讨厌大多数 switch 语句包含不少的 CASE BREAK、CASE BREAK、CASE BREAK……。


老实说,他们讨厌这个是对的,由于人们很容易在这个地方犯错。咱们当中是否有人敢说他们历来没有遇到过这种状况:忘记在 switch 里添加 break 语句,只有当代码在运行时发生崩溃才知道?switch 表达式经过一种有趣的方式修复了这个问题,你只须要用逗号隔开同一个代码块里全部的值。没错,不须要使用 break 了!它会替你处理好!


switch 表达式还新增了 yield 关键字。若是一个 case 进入了一个代码块,yield 将被做为 switch 表达式的返回语句。例如,若是咱们将上面的代码稍做修改:


String adjacentColor = switch (color) {    case Blue, Green -> "yellow";    case Red, Purple -> "blue";    case Yellow, Orange -> "red";    default             -> {    System.out.println("The color could not be found.");    yield "Unknown Color";  }};


在默认 case 里,System.out.println() 方法将被执行,adjacentColor 变量最终的值是“Unknown Color”,由于这是 yield 返回的结果。


总的来讲,switch 表达式是一种更简洁的 switch 语句,但它不会取代 switch 语句,这两种语句均可用。


4:文本块


文本块 特性在 Java 13 中预览,并正式添加到 Java 15 中,它能够简化多行字符串的写法,支持换行,并在不须要转义字符的状况下保持缩进。要建立一个文本块,只须要这样:


String text = """HelloWorld""";


注意,这个变量仍然是一个字符串,只是它隐含了换行和制表符。一样,若是咱们想要使用引号,也不须要转义字符:


String text = """You can "quote" without complaints!"""; // You can "quote" without complaints!


惟一须要使用反斜杠转义字符的地方是当你想要在文本块里包含""":


String text = """The only necessary escape is \""",everything else is maintained.""";


除此以外,你能够调用 String 的 format() 方法,用动态内容替换文本块中的占位符:


String name = "Chris";String text = """My name is %s.""".format(name); // My name is Chris.


每行后面的空格都会被剪切掉,除非你指定了'\s',这是文本块的一个转义字符:


String text1 = """No trailing spaces.Trailing spaces. \s""";


那么,在什么状况下会使用文本块呢?除了可以对大块的文本进行格式化外,将代码片断粘贴到字符串中也变得很是容易。由于缩进被保留了,若是你要写一个 HTML 或 Python 代码块,或使用其余任何语言,你均可以按照正常的方式写好它们,而后用"""把它们括起来,就能够保留代码的格式。你甚至能够用文本块来编写 JSON,并使用 format() 方法轻松地插入值。


总的来讲,这是个一个很方便的特性。虽然文本块看起来只是一个小功能,但从长远来看,相似这种能够提高开发效率的小功能会逐渐增长。


5:record 类


record 类 在 Java 14 中预览,并正式添加到 Java 16 中,是一种数据类,处理全部与 POJO 相关的样板代码。也就是说,若是你声明了一个 record 类:


public record Coord(int x, int y) {}


equals() 和 hashcode() 方法会自动实现,toString() 将返回这个类实例包含的全部字段的值,最重要的是,x() 和 y() 将分别返回 x 和 y 的值。想一想你以前写过的 POJO 类,并想象一下用 record 类来代替它们会怎样。是否是好看多了?省了多少事了?


除此以外,record 类是 final 和不可变的——不能被继承,而且类实例一旦被建立,它的字段就不能被修改。你能够在 record 类中声明方法,包括非静态方法和静态方法:


public record Coord(int x, int y) {  public boolean isCenter() {    return x() == 0 && y() == 0;  }  public static boolean isCenter(Coord coord) {    return coord.x() == 0 && coord.y() == 0;  }}record 类能够有多个构造器:public record Coord(int x, int y) {  public Coord() {    this(0,0); // The default constructor is still implemented.  }}


须要注意的是,当你在 record 类中声明自定义构造函数时,必须调用默认构造函数。不然,record 类将不知道如何处理它的值。若是你声明了一个与默认构造函数同样的构造函数,你要初始化全部的字段:


public record Coord(int x, int y) {  public Coord(int x, int y) {    this.x = x;    this.y = y;  } // Will replace the default constructor.}


关于 record 类,有不少可讨论的话题。这是一个大的变动,在合适的地方使用它们,它们会很是有用。我在这里没有涵盖全部内容,但但愿这能让你了解它们所提供的能力。


6:模式匹配


模式匹配 是 Oracle 在与 Java 冗长语法的斗争中作出的另外一个举措。模式匹配在 Java 14 和 Java 15 中预览过,并正式添加到 Java 16 中,它能够在 instanceof 条件获得知足后消除没必要要的类型转换。例如,咱们都很熟悉这样的代码:


if (o instanceof Car) {  System.out.println(((Car) o).getModel());}


若是你想要访问 Car 的方法,必要要这么作。在第二行,o 是 Car 的实例,这是毫无疑问的,instanceof 已经确认了这一点。若是咱们使用模式匹配,只要作一个小小的改变:


if (o instanceof Car c) {  System.out.println(c.getModel());}


如今,全部的对象类型转换都由编译器完成。看起来改变很小,但它避免了不少样板代码。这也适用于条件分支,当你进入一个已经明确了对象类型的分支:


if (!(o instance of Car c)) {  System.out.println("This isn't a car at all!");} else {  System.out.println(c.getModel());}


你甚至能够在 instanceof 那一行使用模式匹配:


public boolean isHonda(Object o) {  return o instanceof Car c && c.getModel().equals("Honda");}


虽然模式匹配不像其余一些变动那么大,但仍是简化了经常使用的代码。


Java 17 将继续演进


固然,Java 12 到 Java 17 并非只推出了这些更新,这些只是我认为比较有趣的部分。用最新的 Java 版原本运行大型项目须要很大的勇气,若是是从 Java 8 迁移过来,则更须要勇气。


若是有人犹豫不决,是能够理解的。可是,即便你没有迁移计划,或者某个升级计划可能持续数年之久,跟上语言新特性的变化总归是件好事。我但愿我分享的这些内容可以让它们更加深刻人心,让阅读过这些内容的人均可以开始考虑如何使用它们!


Java 17 很特别——它是下一个长期支持版本,接过了 Java 11 的接力棒,而且极可能在将来几年内成为迁移最多的 Java 版本。即便你如今尚未作好准备,能够开始学习起来了,当你身处基于 Java 17 的项目当中,你已是一名经验丰富的开发者!


PS:防止找不到本篇文章,能够收藏点赞,方便翻阅查找哦。