【原创】JDK 9-17新功能30分钟详解-语法篇-var
JDK 9-17新功能30分钟详解-语法篇-var
介绍
JDK 10
JDK 10新增了新的关键字——var,官方文档说作用是:
Enhance the Java Language to extend type inference to declarations of local variables with initializers
大体意思就是用于带有初始化的局部变量声明,废话不多说,我们直接用具体代码来展示实际的作用。
List<String> listBefore10 = new ArrayList<>(); # 在JDK10之前
var listAfter10 = new ArrayList<String>(); # 在JDK10之后
listBefore10.add("9");
listAfter10.add("10");
JDK 11
JDK 11对var做了调整,允许var关键字用于Lambda函数里面的参数类型声明,示例:
var result = Arrays.asList("Java", "11").stream().reduce((var x, var y) -> x + y);
System.out.println(result.orElseThrow());
原理
可以看到使用了var关键字后,节省了一点声明内容,但是仔细一看,例如一个泛型类型从声明部分,挪到了初始化部分去了。我们直接看反编译后的class文件:

可以看到,其实var关键字对于我们来说就是一个语法糖,编译完成后var声明的变量类型已经确定下来了,实际运行的时候是无法起到类似于Javascript语言var声明变量后还能动态更换类型的效果。至于为什么使用必须同时声明和初始化的方式,而不是先声明,后初始化再进行类型推断的方式,官方大体是基于下面考虑的
The majority (more than 75% in both JDK and broader corpus) of local variables with initializers were already effectively immutable anyway, meaning that any "nudge" away from mutability that this feature could have provided would have been limited.
超过75%的JDK库及其相关扩展中,带有初始化的局部变量,都是有效不可变的,即使提供了延后初始化功能起到的作用也不大。
We chose the restriction ... because it covers a significant fraction of the candidates while maintaining the simplicity of the feature and reducing "action at a distance" errors.
使用这种方式既能覆盖绝大数使用场景,又能保持功能简洁,另外一方面也是为了减少可能存在的维护问题,理解的心智成本,例如声明后经过几百行的代码再进行初始化。
具体内容感兴趣的可以看下JEPS 286的Scope Choices部分。
限制
1. 必须初始化
var原理大抵是编译器通过初始化的值推断声明的类型,由此引出使用它的一个约束——声明的同时必须进行初始化。
# 错误示例
var listAfter10;
listAfter10 = new ArrayList<String>();
listAfter10.add("10");
用以上代码直接编译运行,JDK会报错,提示:
java: 无法推断本地变量 listAfter10 的类型
(无法在不带初始化程序的变量上使用 'var')
如果使用IDE,都不用运行就会直接提示你,例如Intellij IDEA:

Cannot infer type:'var' on variable without initializer
回看之前说到的官方声明,“type inference to declarations of local variables with initializers”,with initializers已经很好说明使用它必须初始化,否则编译器无法进行类型推断。
2. 不能为null值
虽然进行初始化,但是使用null值的话,编译器仍然无法进行类型推断确定你最终的类型,也会报错。

Cannot infer type:variable initializer is 'null'
3. 不能用于非局部变量
回看之前说到的官方声明,“type inference to declarations of local variables with initializers”,local variable只能用于局部变量的使用,全局变量或者对象属性声明都不行,例如下面示例是无法正常运行:
# 错误示例
public class Java10 {
public var field = "Not allow here";
}
编译直接报错
此处不允许使用 'var'
4. 不能用于Lambda表达式类型的声明
编译器不支持推断匿名函数的类型,例如:
# 错误示例
var lambdaVar = (String s) -> s != null && s.length() > 0;

Cannot infer type:lambda expression requires an explicit target type
编译直接报错
java: 无法推断本地变量 lambdaVar 的类型
(lambda 表达式需要显式目标类型)
但是这样使用是可以的:
# 正确示例
var lambdaVar = (Function<String, Boolean>) (String s) -> s != null && s.length() > 0;
不过这样写就是脱裤子放屁了,直接写在前面声明不是更好。
亦或者虽然使用了匿名函数,但是其返回值并不是一个Lambda表达式类型,也是可以的。
# 正确示例
var result = Arrays.asList("Java", "10").stream().reduce((x, y) -> x + y);
5. Lambda函数var修饰参数不能与其他类型混合使用
# 错误示例
var result = Arrays.asList("Java", "11").stream().reduce((var x, y) -> x + y);
System.out.println(result.orElseThrow());
# 错误示例
var result = Arrays.asList("Java", "11").stream().reduce((var x, String y) -> x + y);
System.out.println(result.orElseThrow());
就是同一个匿名方法里面要不就都是var修饰,要不就都不用,不能一个用,另外一个不用这种混合使用。当然官方说理论上是可行的,但是由于超出本次JEP规范定义,所以保留这些限制条件。
In theory, it would be possible to have a lambda expression like the last line above, which is semi-explicitly typed (or semi-implicitly typed, depending on your point of view). However, it is outside the scope of this JEP because it deeply affects type inference and overload resolution.This is the main reason for keeping the restriction that a lambda expression must specify all manifest parameter types or none.
使用规范
使用var带来的好处是简化了开发者的局部变量声明成本,但是同时也可能造成代码维护上的不便,特别是开发者和维护者不是同一个人的情况,为此官方也出了一版7个小点的var使用规范。
1. 使用有意义的变量名
# 不规范示例
List<Customer> x = dbconn.executeQuery(query);
# 正确示例
var custList = dbconn.executeQuery(query);
2. 局部变量使用范围尽可能地小
# 不规范示例
var items = new HashSet<Item>(...);
// ... 中间大概隔了几百行的代码 ...
items.add(MUST_BE_PROCESSED_LAST);
for (var item : items) {
...
}
一个方法行数过多,本身已经不利于维护,再加上使用var修饰变量,维护的人可能要鼠标滚动一屏甚至几屏才能看到var变量的具体使用,理解成本大大提高。所以一般情况下var变量保持在一屏内使用就好。
3. 初始化部分有意义时可以使用
var outputStream = new ByteArrayOutputStream();
var reader = Files.newBufferedReader(...);
var stringList = List.of("a", "b", "c");
初始化的部分,例如调用的方法名称或者构造类型名字简单易懂,可以直接使用。
4. 用于拆分链式调用或者嵌套调用
return "test string".stream()
.collect(groupingBy(s -> s, counting()))
.entrySet()
.stream()
.max(Map.Entry.comparingByValue())
.map(Map.Entry::getKey);
上面的链式调用不方便理解或者调试,可以改为
Map<String, Long> freqMap = "test string".stream()
.collect(groupingBy(s -> s, counting()));
Optional<Map.Entry<String, Long>> maxEntryOpt = freqMap.entrySet().stream()
.max(Map.Entry.comparingByValue());
return maxEntryOpt.map(Map.Entry::getKey);
这种情况下可以进一步优化为
var freqMap = "test string".stream().collect(groupingBy(s -> s, counting()));
var maxEntryOpt = freqMap.entrySet().stream().max(Map.Entry.comparingByValue());
return maxEntryOpt.map(Map.Entry::getKey);
5. 不用顾虑用于面向接口开发
List<String> normalList = new ArrayList<>();
var varList = new ArrayList<String>(); # varList最终推断类型是ArrayList<String>而不是List<String>
由于var只能用于局部变量,对于面向接口开发的原则基本无影响,问题主要是var初始化部分的类型依赖,如果发生变化,例如上面示例的ArrayList改成LinkedList,varList的类型随之变化。但是如果遵循规范“2. 局部变量使用范围尽可能地小”的话,影响面就会比较小。
6. 谨慎用于泛型类型
# 正确示例
PriorityQueue<Item> itemQueue = new PriorityQueue<>();
var itemQueue = new PriorityQueue<Item>();
# 不规范示例
var itemQueue2 = new PriorityQueue<>(); # itemQueue2最终推断类型是PriorityQueue<Object>
可能导致类型推断的最终类型不是想要的泛型类型。
7. 谨慎用于字面量
byte flags = 0;
short mask = 0x7fff;
long base = 17;
改成
var flags = 0;
var mask = 0x7fff;
var base = 17;
全部类型都会推导为int。
【原创】JDK 9-17新功能30分钟详解-语法篇-var的更多相关文章
- Unity2017新功能Sprite Atlas详解
Sprite Atlas(精灵图集)Sprite Atlas 针对现有的图集打包系统Sprite Packer在性能和易用性上的不足,进行了全面改善.除此之外,相比Sprite Packer,Spri ...
- 史上!最最最简洁明了的 Java JDK 安装目录及其子目录含义 10分钟详解 - 精简归纳
Java JDK 安装目录及其子目录含义 10分钟详解 - 精简归纳 JERRY_Z. ~ 2020 / 8 / 30 转载请注明出处!️ 目录 Java JDK 安装目录及其子目录含义 10分钟详解 ...
- 深入浅出 Java JDK 安装目录及其子目录含义 10分钟详解 - 精简归纳
Java JDK 安装目录及其子目录含义 10分钟详解 - 精简归纳 JERRY_Z. ~ 2020 / 8 / 30 转载请注明出处!️ 目录 Java JDK 安装目录及其子目录含义 10分钟详解 ...
- mysql数据库分区功能及实例详解
分区听起来怎么感觉是硬盘呀,对没错除了硬盘可以分区数据库现在也支持分区了,分区可以解决大数据量的处理问题,下面一起来看一个mysql数据库分区功能及实例详解 一,什么是数据库分区 前段时间写过一篇 ...
- Flex布局新旧混合写法详解(兼容微信)
原文链接:https://www.usblog.cc/blog/post/justzhl/Flex布局新旧混合写法详解(兼容微信) flex是个非常好用的属性,如果说有什么可以完全代替 float 和 ...
- Hadoop 新 MapReduce 框架 Yarn 详解
Hadoop 新 MapReduce 框架 Yarn 详解: http://www.ibm.com/developerworks/cn/opensource/os-cn-hadoop-yarn/ Ap ...
- Java 5 的新标准语法和用法详解集锦
Java 5 的新标准语法和用法详解集锦 Java 5 的新标准语法和用法详解集锦 (需要在首选项-java-complier-compiler compliance level中设置为java5.0 ...
- Delphi Format函数功能及用法详解
DELPHI中Format函数功能及用法详解 DELPHI中Format函数功能及用法详解function Format(const Format: string; const Args: array ...
- Flex 布局新旧混合写法详解(兼容微信)
flex 是个非常好用的属性,如果说有什么可以完全代替 float 和 position ,那么肯定是非它莫属了(虽然现在还有很多不支持 flex 的浏览器).然而国内很多浏览器对 flex 的支持都 ...
随机推荐
- 关于spring整合mybatis
第一步导入依赖 <dependencies> <dependency> <groupId>org.mybatis</groupId> <artif ...
- Fiddler对安卓高版本进行抓包解决方案以及分析 进阶二
今天是2021年的最后一天了,多分享一些干货吧!看过上一章节教程后会有同学疑惑,我也一步一个脚印的,跟着流程走也设置了代理以及安装了证书,有的同学会发现 为什么手机不能够连接网络了呢?细心一点的同学会 ...
- D3.JS结合Canvas实现直方图,散点图,等高线图,密度图
接触到D3.JS,感觉在图表方面实现的很好,于是深入了解了一下,想在项目中使用, 可是当看到DEMO时才发现,基本上所有的DEMO都是基于SVG,虽然D3.JS声称支持CANVAS,可并没有发现一例使 ...
- php原生PHPExcel插件导表(附表格合并,加粗居中及加边框换行操作)
PHPExcel是用来操作Office Excel文档的一个PHP类库,它基于微软的OpenXML标准和PHP语言.可以使用它来读取.写入不同格式的电子表格,如 Excel(BIFF) .xls, E ...
- go-zero微服务实战系列(四、CRUD热热身)
上一篇文章我们把整个项目的架子搭建完成,服务在本地也已经能运行起来了,顺利成章的接下来我们就应该开始写业务逻辑代码了,但是单纯的写业务逻辑代码是比较枯燥的,业务逻辑的代码我会不断地补充到 lerbon ...
- 【Redis】ziplist压缩列表
压缩列表 压缩列表是列表和哈希表的底层实现之一: 如果一个列表只有少量数据,并且数据类型是整数或者比较短的字符串,redis底层就会使用压缩列表实现. 如果一个哈希表只有少量键值对,并且每个键值对的键 ...
- 1.1 操作系统的第一个功能——虚拟化硬件资源 -《zobolの操作系统学习札记》
1.1 操作系统的第一个功能--虚拟化硬件资源 目录 1.1 操作系统的第一个功能--虚拟化硬件资源 问1:操作系统一般处于计算机系统的哪一个位置? 问2:管理硬件资源为什么要单独交给操作系统? 问3 ...
- SAP FPM 相关包 APB_FPM_CORE
related interface: APB_FPM_COREAPB_FPM_CORE_4_EXT_SCAPB_FPM_CORE_INTERNALAPB_FPM_CORE_RESTRICTED
- 如何写好测试用例以及go单元测试工具testify简单介绍
背景 最近在工作和业余开源贡献中,和单元测试接触的比较频繁.但是在这两个场景之下写出来的单元测试貌似不太一样,即便是同一个代码场景,今天写出来的单元测试和昨天写的也不是很一样,我感受到了对于单元测 ...
- java SWT中Label实时刷新当前时间
同样最近在开发swt的一个项目,业务中的一个功能模块类似百度网盘的上传进度条 0/80. 即已上传0个,总共80个.效果展示要的就是实时刷新,2/80呀,15/80呀,针对这个,就有了这篇文章. 下面 ...