有了它,你再也不用写那个繁琐的变量类型了

局部变量类型推断是 Java 10 中引入的一项重要特性,通过使用var关键字,允许我们在声明局部变量时省略显式类型。类型推断意味着编译器会查看变量的初始化器并推断出变量的类型

产生背景

刚刚学 Java 语法时,我们就被告知:在 Java 中,所有的变量在使用前必须声明,所以我们就有了如下代码:

ini 复制代码
int i = 10;
String str = "死磕 Java 新特性";
List<String> list = new ArrayList<>();

甚至很多小伙伴已经养成了习惯,在声明变量时永远都是从左写到右,即先写变量类型,然后变量名,最后初始化,这样写有问题吗?没有问题,但是比较繁琐,简单的变量类型还可以接受,但是如果复杂呢?比如这个:

javascript 复制代码
Map<String, List<String>> myMap = new HashMap<>();

又或者这个:

javascript 复制代码
List<Map<String,List<String>>> list = new ArrayList<>();

确实很繁琐,对于很多变量声明通常需要重复类型信息,这显得冗余又不增加任何价值。而且许多其他现代编程语言,如Scala、Kotlin和C#,都已经提供了类型推断的能力,Java 作为一门与时俱进的现代化编程语言,怎么能不跟进呢?

好处

  1. 减少样板代 :减少了啰嗦和形式的代码,避免了信息冗余,使得代码更易于编写和阅读。例如用var list = new ArrayList<String>();代替ArrayList<String> list = new ArrayList<>();
  2. 增强代码的可读性 :很多时候我们在阅读代码时并不关注变量的类型,或者变量类型显而易见,而 var 使得变量的意图变得更清晰。
  3. 提高开发效率:减少编写显式类型所需的时间和努力,可以让开发者更专注于业务逻辑本身,毕竟太多的类型声明只会分散注意力,不会带来额外的好处。

使用场景

局部变量声明

在局部代码块中声明临时变量,特别是当这些变量的类型很明显时,使用var可以减少代码的冗余。比如

ini 复制代码
var i = 10;
var str = "死磕 Java 新特性";

这是最简单明了的使用方式 了,尤其是局部变量的类型常常非常长或复杂,使用var能够使代码更简洁。。

初始化集合和泛型表达式

在使用集合或泛型类时,类型通常很复杂,使用 var 可以避免在声明和初始化时重复复杂的类型。

javascript 复制代码
List<String> list = new ArrayList<>();
// 变成
var list = new ArrayList<String>();

Map<String,List<String>> map = new HashMap<>();
// 变成
var map = new HashMap<String,List<String>>();

在 Java 7 以前,我们声明集合变量时需要这样写:

arduino 复制代码
 List<String> list = new ArrayList<String>();    // 两侧都需要加上泛型类型

可能你觉得在声明变量的时候已经指明了参数类型,为什么还要在初始化对象时再指定?所以在 Java 7 中引入泛型类型推断,于是写法可以简化这样的:

arduino 复制代码
List<String> list = new ArrayList<>();    // 只需要在左侧加上泛型类型

编译器会根据变量声明时的泛型类型自动推断出实例化 List 时的泛型类型。但是我们一定要加上 "<>",只有加上这个 "<>" 才表示是自动类型推断。

现在 Java 10 又引入了局部变量类型推断,所以又可以进一步简化:

ini 复制代码
var list = new ArrayList<String>();

可能有小伙伴会问,可以写成下面这种格式么:

ini 复制代码
var list = new ArrayList<>();

可以,只不过这个时候类型就变成了 Object:

遍历操作

迭代时,使用 var 可以避免编写冗长的类型名称。例如:

  • 常规写法
csharp 复制代码
    @Test
    public void varTest() {
        var list = new ArrayList<String>();
        list.add("死磕 Java 新特性");
        list.add("死磕 Java 并发");
        list.add("死磕 Netty");
        
        for (String str : list) {
            System.out.println(str);
        }
    }

for-each 里面指明了变量 str 的类型,但是我们明明都知道 list 的类型是 String,为什么还要指明呢?不是多此一举么:

csharp 复制代码
    @Test
    public void varTest() {
        //

        for (var str : list) {
            System.out.println(str);
        }
    }

在try-with-resources语句中

try-with-resources要求变量是final或事实上的final,在这里使用var可以减少冗余代码。比如:

csharp 复制代码
try (var reader = new BufferedReader(new FileReader(path))) {
    // 使用reader
}

不适用场景

虽然局部变量类型推断可以提高代码的简洁性,但在某些情况下使用var可能并不合适,因为它可能会降低代码的可读性和可维护性。

  • 使用null初始化变量时

不能使用 var 来声明一个初始化为 null 的变量,因为 var 需要足够的信息来推断出变量的类型。

csharp 复制代码
var object = null;   // 错误的使用方式,因为 null 不能推断出具体类型
  • 需要使用具体类特定方法时

如果需要调用一个特定类的方法,而这个方法不是由其所有可能的子类共有的,那么最好显式声明这个类类型。比如:

  • 类型信息很重要

有些时候变量的类型对于我们理解代码很重要,这个时候我们就不能省略变量的类型,尽管可以省略。

  • 数组静态初始化

数组静态初始化是不能省略的。

csharp 复制代码
int[] arr1 = new int[]{1,2,3,4,5}      // 情况①:正常情况
int[] arr1 =  {1,2,3,4,5};             // 情况②:从左边推断后面,可以省略
var arr2 = new int[]{1,2,3,4,5};       // 情况③:从右边推断前面,可以省略
var arr3 = {1,2,3,4,5}                 // 情况④:无法推断,不能省略

这里这是一些场景,还有其他一些场景也是不能使用的,我们把握一个核心原则就行:能够让编译器推断出是什么类型的都可以使用

注意事项

  • 使用类型推断时,必须在变量声明时初始化变量,这样编译器才能推断出类型。它只适用于局部变量,不能用于方法参数、返回类型、字段等。
  • 类型推断可能使得代码阅读者不清楚变量的实际类型,尤其是当初始化器的表达式不直接显露类型时。应当避免过度使用 var
相关推荐
candyTong1 天前
RTK 技术原理:一次典型会话里,80% 上下文是怎么省下来的
javascript·后端·架构
Rust研习社1 天前
组合真的优于继承吗?为什么 Rust 和 Go 都拥抱组合舍弃继承?
后端·rust·编程语言
IT_陈寒1 天前
JavaScript的闭包把我坑惨了,说好的内存会自动回收呢?
前端·人工智能·后端
CaffeinePro1 天前
Pydantic深度使用:数据校验、枚举、ORM映射
后端·fastapi
Chenyiax1 天前
从 Chat 到 Responses:OpenAI API 抽象为什么变了?
后端
MariaH1 天前
Koa和Express的区别
后端
MariaH1 天前
Koa框架的使用
后端
luckdewei1 天前
那个用 passlib 做认证的新同事,上线第一天就把用户密码写进了日志
后端
ping某1 天前
为什么 Nginx 明明监听了 80,转发后端时却用了 4xxxx 端口?
后端·nginx
JustHappy1 天前
我汇总了身边朋友的经历才发现,其实第一份实习是最难找的......
前端·后端·面试