【AICon】探索八个行业创新案例,教你在教育、金融、医疗、法律等领域实践大模型技术! >>> 了解详情
写点什么

自 Java 8 以来的新语言特性

  • 2019-08-16
  • 本文字数:4023 字

    阅读完需:约 13 分钟

自Java 8以来的新语言特性


本文介绍了自 Java 8 以来与 Java 语言相关的改进。密切关注 Java 平台是很重要的,因为按照新的快速发布节奏,每六个月就会发布一个新的 Java 版本对平台和语言进行更改。


当 Java 8 引入 StreamsLambdas 时,那是一个巨大的变化,使得函数式编程风格可以用更少的样板代码来表达。虽然最近的版本没有添加这样有影响的特性,但是也对该语言进行了许多较小的改进。


本文总结了 Java 8 之后发布的 Java 版本中包含的语言增强。有关所有塑造新平台的 JEP 的概述,请查看这篇博文。

局部变量类型推断

自 Java 8 以来,最显著的语言改进可能是添加了关键词 var。它最初是在 Java 10 中引入的,并在 Java 11 中得到了进一步的改进。


该特性允许我们通过省略显式类型说明来简化局部变量声明的过程:


var greetingMessage = "Hello!";
复制代码


虽然它看起来类似于 JavaScript 的 var 关键字,但这与动态类型无关。


引用 JEP 的一句话:


我们寻求通过减少与编写 Java 代码相关的仪式来改进开发人员的体验,同时保持 Java 对静态类型安全性的承诺。


声明变量的类型在编译时进行推断。在上面的示例中,推断出的类型是 String。使用 var 而不是显式类型可以减少这段代码的冗余,因此更容易阅读。


下面是另一个很适合类型推断的场景:


MyAwesomeClass awesome = new MyAwesomeClass();
复制代码


很明显,在很多情况下,这个特性可以提高代码质量。然而,有时坚持使用显式类型声明会更好。让我们看几个用 var 替换类型声明可能适得其反的例子。

注意可读性

第一种情况是,从源代码中删除显式类型信息会降低可读性。


当然,IDE 可以在这方面提供帮助,但是在代码评审期间,或者在快速扫描代码时,它可能会破坏可读性。例如,考虑下工厂或构建器:你必须找到负责对象初始化的代码来确定类型。


这里有个小谜题。下面这段代码使用 Java 8 的日期 / 时间 API。猜猜下面代码片段中变量的类型:


var date = LocalDate.parse("2019-08-13");var dayOfWeek = date.getDayOfWeek();var dayOfMonth = date.getDayOfMonth();
复制代码


好了吗?答案是这样的:


第一个非常直观,parse 方法返回一个 LocalDate 对象。但是,对于接下来的两个方法,你需要更熟悉这些 API:dayOfWeek 返回 java.time.DayOfWeek,而 dayOfMonth 只返回一个 int。


另一个潜在的问题是,使用 var,读者不得不更多地依赖上下文。考虑以下代码片段:


private void horriblyLongMethod() {    // ...    // ...    // ...
var dayOfWeek = date.getDayOfWeek();
// ... // ... // ...}
复制代码


根据前面的例子,我敢打赌你一定会猜到它是 java.time.DayOfWeek 类型。但这次,它是一个整数,因为本例中的日期来自 Joda time。它是一个不同的 API,行为略有不同,但你看不到它,因为它是一个比较长的方法,而且你没有阅读所有的代码行。(JavaDoc:Joda time/Java 8 Date/ Time API)


如果显式类型声明存在,那么弄清楚 dayOfWeek 的类型就很简单了。现在,使用 var,读者首先必须找出 date 变量的类型并检查 getDayOfWeek 做了什么。这在 IDE 中很简单,但在扫描代码时就不那么简单了。

注意保留重要的类型信息

第二种情况是,当使用 var 删除所有可用的类型信息时,甚至都无法推断出来。在大多数情况下,这些情况是由 Java 编译器捕获的。例如,var 不能为 Lambda 表达式或方法引用推断类型,因为对于这些特性,编译器依赖于左侧表达式来推断类型。


然而,也有一些例外。例如,var 不能很好地处理 Diamond 操作符。Diamond 操作符是一个很好的特性,可以在创建泛型实例时消除表达式右侧的一些冗余:


Map<String, String> myMap = new HashMap<String, String>(); // 在Java 7之前Map<String, String> myMap = new HashMap<>(); // 使用Diamond操作符
复制代码


因为它只处理泛型类型,所以仍然有冗余需要删除。让我们试着用 var 使它更简洁:


var myMap = new HashMap<>();
复制代码


这个例子是有效的,Java 11 甚至没有在编译器中发出关于它的警告。然而,使用所有这些类型推断,我们最终根本没有指定泛型类型,类型将是 Map<Object, Object>。


当然,这可以通过删除 Diamond 操作符轻松解决:


var myMap = new HashMap<String, String>();
复制代码


当 var 与原始数据类型一起使用时,可能会出现另一组问题:


byte   b = 1;short  s = 1;int    i = 1;long   l = 1;float  f = 1;double d = 1;
复制代码


如果没有显式的类型声明,所有这些变量的类型将被推断为 int。在处理基本数据类型时,使用字面类型(例如 1L),或者在这种情况中根本不使用 var。

请务必阅读官方风格指南

最终由你决定何时使用类型推断,并确保它不会损害可读性和正确性。作为一个经验法则,坚持良好的编程实践,比如良好的命名和最小化局部变量的作用域。务必要阅读官方风格指南和 FAQ 中关于 var 的部分。


因为 var 有太多的陷阱,所以它的引入比较保守,并且只能用于局部变量,而局部变量的作用域通常非常有限。


此外,它被谨慎地引入,var 不是一个新的关键字,而是一个保留类型名。这意味着只有当它作为类型名使用时,它才具有特殊的意义,在其他任何地方,var 都将继续作为有效标识符。


目前,var 没有一个不可变对应项(如 val 或 const)来声明一个最终变量,并用一个关键字来推断它的类型。我们希望将来的版本,在那之前,我们可以使用 final var。


相关资源:


来自 Milling Project Coin 的各种改进

Coin 项目(JSR 334)是 JDK 7 的一部分,它带来了一些方便的语言改进:


  • Diamond 操作符

  • Try-with-resources 语句

  • 多异常捕获和更准确地异常重抛

  • 将 String 用于 switch 语句

  • 二进制整数字面值和数字字面值中的下划线

  • 简化的 Varargs 方法调用


Java 9 继续沿着这条道路前进,并添加了一些更小的改进。

允许在接口中声明私有方法

因为 Java 8 可以向接口添加默认方法。在 Java 9 中,这些默认方法甚至可以调用私有方法来共享代码,从而在需要时重用,但又不想公开暴露功能。


虽然这不是一个大问题,但它是一个逻辑上的补充,让你可以在默认方法中整理代码。

匿名内部类中的 Diamond 操作符

Java 7 引入了 Diamond 操作符(<>),通过让编译器推断构造函数的参数类型来简化代码:


List<Integer> numbers = new ArrayList<>();
复制代码


但是,这个特性以前不能用于匿名内部类。根据项目邮件列表的讨论,这不是作为原始 Diamond 操作符特性的一部分添加的,因为它需要大量的 JVM 更改。


在 Java 9 中,这个小瑕疵得到了完善,使得该操作符更加通用:


List<Integer> numbers = new ArrayList<>() {    // ...}
复制代码

允许将有效 final 变量作为 try-with-resources 语句的资源

Java 7 引入的另一个增强是 try-with-resources,它使开发人员不必担心资源的释放。


为了说明它的强大功能,首先考虑下在 Java 7 之前,下面这个典型的例子为正确关闭资源所做的工作:


BufferedReader br = new BufferedReader(...);try {    return br.readLine();} finally {    if (br != null) {        br.close();    }}
复制代码


借助 try-with-resources,资源可以自动释放,大大减少了仪式代码(ceremony):


try (BufferedReader br = new BufferedReader(...)) {    return br.readLine();}
复制代码


尽管具有强大的功能,但是 try-with-resources 有一些缺点在 Java 9 中才得以解决。


尽管这个构造可以处理多个资源,但它很容易使代码更难阅读。与通常的 Java 代码相比,在 try 关键字之后在列表中声明这样的变量有点非常规:


try (BufferedReader br1 = new BufferedReader(...);    BufferedReader br2 = new BufferedReader(...)) {    System.out.println(br1.readLine() + br2.readLine());}
复制代码


同样,在 Java 7 版本中,如果已经有一个你想要处理的变量采用了这个构造,就必须引入一个虚拟变量。(示例参见 JDK-8068948)。


为了减少批评,除了新创建的变量外,经过增强的 try-with-resources 可以处理 final 或有效 final 局部变量:


BufferedReader br1 = new BufferedReader(...);BufferedReader br2 = new BufferedReader(...);try (br1; br2) {    System.out.println(br1.readLine() + br2.readLine());}
复制代码


在本例中,变量的初始化与它们在 try-with-resources 构造的注册分离。


需要注意的是,现在可以引用已经由 try-with-resources 释放的变量,这在大多数情况下会失败:


BufferedReader br = new BufferedReader(...);try (br) {    System.out.println(br.readLine());}br.readLine(); // Boom!
复制代码


下划线不再是有效的标识符名称 在 Java 8 中,当使用“_”作为标识符时,编译器会发出警告。Java 9 更进一步,使单个下划线字符作为标识符非法,并保留这个名称以便将来用于特殊的语义:


int _ = 10; // Compile error
复制代码

改进警告信息

最后,让我们简单介绍一下与最新 Java 版本中与编译器警告相关的更改。


现在可以使用 @SafeVarargs 注解一个私有方法来标记“Type safety: Potential heap pollution via varargs parameter”警告为假阳性。(事实上,此更改是前面讨论的 JEP 213:Milling Coin 项目的一部分)。要了解更多关于 Varargs、泛型以及组合这些特性可能出现的潜在问题的信息,请阅读官方文档。


同样,从 Java 9 开始,当导入不推荐使用的类型时,编译器不会针对 import 语句发出警告。由于在实际使用不推荐使用的成员时总是会显示单独的警告,所以这些警告没有提供足够的信息,而且是多余的。

总结

本文介绍了自 Java 8 以来与 Java 语言相关的改进。密切关注 Java 平台是很重要的,因为按照新的快速发布节奏,每六个月就会发布一个新的 Java 版本对平台和语言进行更改。


关于作者 David 是一名全堆栈开发人员,具有 10 多年的 Java 和 Web 技术经验。长期以来,他一直提倡开源和测试自动化。他拥有硕士学位,多年来参加了无数的在线课程。你还可以从 Scott Maven 插件和 Vim 插件 Disapprove Deep Indentation 中找到 David 的名字。

原文链接:

https://advancedweb.hu/2019/08/08/post_java_8_language_features/


2019-08-16 17:035443
用户头像

发布了 689 篇内容, 共 399.0 次阅读, 收获喜欢 1498 次。

关注

评论 1 条评论

发布
用户头像
技术更新越来越少了!技术更新时间越来越快了。
2019-08-16 17:49
回复
没有更多了
发现更多内容

开源推荐:国内3大主流前端UI表单设计器,千万不要让领导知道

互联网应用架构

Vue Element antd

架构师训练营第二周作业

吴传禹

极客大学架构师训练营

软件设计的基本原则

天天向上

极客大学架构师训练营

架构师训练营 Week2 总结

lggl

总结 极客大学架构师训练营

食堂就餐卡系统设计-作业

Kenny

作业

架构师训练营第 1 期 -- 第二周作业

发酵的死神

极客大学架构师训练营

架构师训练营第二周课程笔记及心得

Airs

架构师训练营第 1 期 -- 第二周学习总结

发酵的死神

极客大学架构师训练营

第二周作业

alpha

极客大学架构师训练营

架构一期 - 甘霖 - Week2 - 作业一

小粽

训练营第二周作业1

Yangjing

极客大学架构师训练营

第二周 框架设计作业

蓝黑

极客大学架构师训练营

什么是依赖倒置原则,为什么有时候依赖倒置原则又被称为好莱坞原则?

魏小龙

敏捷开发 依赖倒置原则

第二周总结

睁眼看世界

极客大学架构师训练营

依赖倒置原则

知行合一

软件设计原则

荷之美 | 中国荷苑

xcbeyond

生活 摄影 摄影征文 荷花

架构师训练营学习总结——第二周

文智

极客大学架构师训练营

week1--作业一

hero_genlot

极客大学架构师训练营

十七张图玩转Node进程——榨干它

执鸢者

大前端 进程 Node

架构师训练营 1 期 -- 第二周总结

曾彪彪

极客大学架构师训练营

Spring 5 中文解析数据存储篇-JDBC数据存储(上)

青年IT男

Spring5

Mac mini 2020上手体验

李印

Mac

架构师训练营第 1 期 - 第二周学习总结

Anyou Liu

极客大学架构师训练营

极客大学架构师训练营第二周课后总结

jizhi7

依赖倒置及接口隔离原则

天天向上

极客大学架构师训练营

回首挑灯看剑谱 - Week2 - 学习总结

小粽

架构师训练营第二周学习感悟

吴传禹

极客大学架构师训练营

架构师01期,第二周课后作业

子文

架构一期第二周作业

Airs

第二周课后练习

jizhi7

架构师训练营,第二周总结

子文

自Java 8以来的新语言特性_语言 & 开发_Dávid Csákvári_InfoQ精选文章