东亚银行、岚图汽车带你解锁 AIGC 时代的数字化人才培养各赛道新模式! 了解详情
写点什么

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

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

关注

评论 1 条评论

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

百度飞桨EasyDL桌面版正式上线,没网也能训练AI!

百度大脑

人工智能

ALC北京发起人 姜宁:通过开放与协作,我们可以实现一个人想都不敢想的事情 I OpenTEKr 大话开源 Vol.6

OpenTEKr

大话开源

盘点 2021|自己一个人扛起了公司的半边天

liuzhen007

技术人生 盘点2021 盘点 2021

VS Code 如何设置大小写转换快捷键

AlwaysBeta

vscode

【架构实战营】模块三:知识点总结

wgl

「架构实战营」

深入理解一下Python中的面向对象编程

宇宙之一粟

Python 面向对象 12月日更

详细架构设计文档

Anlumina

#架构实战营

Golang中文件的基本操作

liuzhen007

Go 28天写作 Go 语言 12月日更

第三模块学习总结

Anlumina

#架构实战营

「架构实战营」模块三《如何保证设计出合理的架构》作业

DaiChen

作业 模块三 「架构实战营」

从人工到智能!百度AI开发者大会分论坛,探寻国球乒乓背后的AI之路

百度大脑

人工智能

一口气搞懂【Linux内存管理】,就靠这60张图、59个问题了

奔着腾讯去

内存泄露 内存管理 Linux Kenel 内存映射 内存池

瞰见 | 初创1个月就融到3亿美金,ClickHouse 你凭什么?

OpenTEKr

狄安瞰源

架构实战 模块三作业

mj4ever

架构实战

架构实战营4期-模块3作业

木几丶

「架构实战营」

学习总结 2021.12.30

mj4ever

学习笔记

Git基础 |打tag

xcbeyond

git 28天写作 tag 12月日更

架构实战营模块三作业

lchx08

「架构实战营」

架构实战营:模块三作业

Geek_93ffb0

「架构实战营」

引领人工智能技术自立自强 百度吴甜获评“首都最美巾帼奋斗者”

百度大脑

人工智能「

瞰见 | 开源,会不会变成开源创业的焦油坑?

OpenTEKr

狄安瞰源

Apache 海豚调度 PMC 郭炜:开源,不是天才的甜点,而是执着者的盛宴 I OpenTEKr 大话开源 Vol.7

OpenTEKr

大话开源

【架构实战营】模块三:命题作业

wgl

「架构实战营」

外包学生管理系统详细设计文档

糖糖学编程

架构实战营

费用节省 50%,函数计算 FC 助力分众传媒降本增效

阿里巴巴云原生

阿里云 云原生 合作 函数计算FC 分众传媒

百度智能云发布零碳园区解决方案,助力实现双碳目标

百度大脑

人工智能

阿里巴巴超大规模 Kubernetes 基础设施运维体系揭秘

阿里巴巴云原生

阿里云 Serverless Kubernetes 云原生 ASI

第三周学习总结

糖糖学编程

架构实战营

一个cpp协程库的前世今生(一)缘起

SkyFire

协程 cpp cocpp

元宇宙很好,但VR开发者不准备停留在这里

白洞计划

Java 数据持久化系列之池化技术

程序员历小冰

MySQL 持久化 28天写作 池化技术 12月日更

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