写点什么

自 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:035595
用户头像

发布了 720 篇内容, 共 454.9 次阅读, 收获喜欢 1535 次。

关注

评论 1 条评论

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

中心化决议管理——云端分析

字节跳动终端技术

ios 研发效能 CocoaPods 制品库 云化服务

【编程基础】利用Python实现阿姆斯特朗数的求解

迷彩

Python 9月月更 阿姆斯特朗数 水仙花数

融云x白鲸《2022社交泛娱乐出海白皮书》

融云 RongCloud

互联网 白皮书 融云

com.alibaba.fastjson 对象转json剔除字段

六月的雨在InfoQ

问题处理 Fastjson index Elastic Search 9月月更

Java | this和super关键字【深入理解子类和父类的继承关系】

Fire_Shield

super this 9月月更

NFTScan 与 Banksea Finance 在 NFT 源数据层面达成战略合作

NFT Research

区块链 NFT 合作 web3

数据产品经理那点事儿三(合集)

松子(李博源)

大数据 深度思考 高效工作 数据产品经理

面试了一位4年Java的程序员,张口就要35K,还什么都不会...

收到请回复

Java 语言 & 开发 八股文

精品!阿里P7爆款《K8s+Jenkins》技术笔记,高质量干货必收藏

程序知音

HTTP - TLS1.3 初次解读

懒时小窝

概述数据交换的构建策略

穿过生命散发芬芳

数据交换 9月月更

IP地址和MAC地址都可以确定目标地址,为什么二者都在使用,舍弃一个是否可行?

阿柠xn

Mac IP 网络 协议族 9月月更

一比一手写迷你版vue,彻底搞懂vue运行机制

hellocoder2029

JavaScript

手写vue-router核心原理

hellocoder2029

Vue

开发者有话说|成长之路

六月的雨在InfoQ

个人成长 开会 996 007 9月月更

架构实战训练营模块1作业--开启架构之旅

阿姆斯壮

架构实战营 #架构实战营

SAE 助力贵州酒店集团从容支撑贵州特产抢购

阿里巴巴中间件

阿里云 Serverless 云原生 SAE

软件开发正确打开方式:低代码+微服务

力软低代码开发平台

开发者测评:相比 Harbor,我选择 ACR 的三点原因

阿里巴巴云原生

阿里云 Kubernetes 容器 云原生 ACR

字节、美团、滴滴以及蚂蚁金服Java后端面试过程

收到请回复

Java 程序员 面试 项目 语言 & 开发

不会还有程序员不知道跳槽季靠这1700道java面试题就能平淌大厂吧

程序知音

Java java面试 后端技术 秋招 Java面试题

开发者有话说|一名高中生的编程之路

Loken

个人成长

Fluid 助力阿里云 Serverless 容器极致提速

阿里巴巴云原生

阿里云 Serverless 云原生 Fluid ASK

你用对了么?对象文件网关 VS 分布式文件存储

焱融科技

云计算 分布式系统 对象存储 高性能 文件存储

龙湖千丁基于 ACK@Edge 的云原生智慧停车系统架构实践

阿里巴巴云原生

阿里云 Kubernetes 云原生

2022秋招最新整理上千道Java面试攻略,近500页PDF文档

收到请回复

程序员 Java 面试 跳槽 语言 & 开发 秋招

LED显示屏价格与品质哪个更重要

Dylan

LED LED显示屏 led显示屏厂家

信创升级 | 秒云与人大金仓完成兼容性互认证

MIAOYUN

数据库 信创 国产数据库 信创云 容器云平台

数据产品经理那点事儿二(合集)

松子(李博源)

大数据 深度思考 高效工作

5G网络行业切片SLA初探

鲸品堂

5G 网络切片

旷世巨作!20多位架构师携手打造的“Java 面试核心宝典”限时开源

Geek_0c76c3

Java 数据库 开源 程序员 架构

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