案例分享:阿里巴巴全球化电商核心系统架构实战,点击学习>>> 了解详情
写点什么

自 Java 8 以来的新语言特性

2019 年 8 月 16 日

自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 年 8 月 16 日 17:035013
用户头像

发布了 383 篇内容, 共 168.4 次阅读, 收获喜欢 904 次。

关注

评论 1 条评论

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

算法刷题提醒——微信小程序 [持续优化ing]

小匚

深度思考 随笔杂谈 成长与思考

一文帮你搞懂 Android 文件描述符

vivo互联网技术

android 文件 文件系统

基于GES图数据库的大规模数据追溯服务优化

华为云开发者社区

数据 华为云 图数据库 数据追溯 华为云GES

如何实现在直播中播放音频文件

anyRTC开发者

音视频 WebRTC 直播 RTC 音频

【LeetCode】比特位计数Java题解

HQ数字卡

算法 LeetCode 28天写作

来自农村的大学生开发者,用技术改变自己的家乡

华为云开发者社区

开发者 物联网 IoT 华为云 智慧大棚

一文详解什么是可解释AI

华为云开发者社区

mindspore 可解释AI Gradient GradCAM RISE

云计算、大数据已经过时?不,正是因为它们RPA才能大流行

王吉伟频道

人工智能 云计算 大数据 RPA 自动化

Elasticsearch search scroll 游标查询

escray

elastic 七日更 28天写作 死磕Elasticsearch 60天通过Elastic认证考试 3月日更

To B产品经理需要哪些能力呢?

博文视点Broadview

MySQL异常问题经验贴

华为云开发者社区

MySQL 数据库 时区 SSL 连接

Atlassian Data Center 如何优化企业中新员工的远程入职流程

Atlassian

HR Atlassian 远程工作

力扣(LeetCode)刷题,简单+中等题(第28期)

不脱发的程序猿

面试 LeetCode 编程之路 28天写作 算法面经

架构学习20210302日(001)

张小胖

玩家永远是对的——认知失调

Justin

心理学 28天写作 游戏设计

数仓GaussDB(DWS)全量备份总结

华为云开发者社区

数据安全 GaussDB 备份 Roach 数据备份

ICDAR2021首届文档图像与自然语言处理研讨会征稿开始

爱极客侠

产品更新 | 阿里云CDN边缘图像处理功能开放内测

阿里云Edge Plus

CDN 边缘计算 图像处理

产品经理如何帮助减少技术债务 ?

禅道项目管理

产品 代码规范 技术债

一个100%省力的,让城市管廊运维变得轻松的秘诀

一只数据鲸鱼

物联网 数据可视化 智慧城市 3D可视化 智慧管廊

滚动加载的网页只需点 10 下鼠标即可抓取,无编码学爬虫之四

梦想橡皮擦

Python 28天写作 3月日更

【万字好文】一文看懂持续部署按需发布!DevOps部署和发布方法大全

京东科技开发者

DevOps SaaS

区块链助力山东文化旅游整体行业解决方案

源中瑞-龙先生

Pgbouncer最佳实践:系列三

PostgreSQLChina

数据库 postgresql 软件 开源社区

第五周

Jove

在云中应用自动化的5种方法

浪潮云

云计算

Linux 多线程详解 —— 线程创建、终止、等待、分离

赖猫

Linux 多线程与高并发 服务器开发 Linux服务器开发 Linux线程

力扣(LeetCode)刷题,简单+中等题(第29期)

不脱发的程序猿

面试 LeetCode 编程之路 28天写作 算法面经

第五六周心得

Trigger

极客时间 产品经理训练营

产品文档总结

mas

可视化表单编辑器

day day up

数据库运维技术发展与展望

数据库运维技术发展与展望

自Java 8以来的新语言特性-InfoQ