← 返回题目列表

Java 的 var 是什么?它让 Java 变成动态类型语言了吗?

简单 第 18 / 24 题 更新于 2026/07/26
var类型推断局部变量Java10

简化版

var(Java 10 引入)是局部变量的类型推断——你写 var list = new ArrayList<String>(),编译器会根据右边的表达式自动推断出 list 的类型是 ArrayList<String>,你不用重复写一遍类型。它不是动态类型、不是弱类型——var 变量的类型在编译期就确定死了、之后不能变,只是「省得你手写类型」的语法糖。var list 推断成 ArrayList 后,就不能再 list = "hello"(类型不匹配,编译报错)。所以 Java 用了 var 仍然是静态强类型语言var 只是让代码更简洁,尤其是类型名很长时(如 Map<String, List<Integer>>)。

详细版

var 的作用:省略重复的类型声明

// 传统写法:类型写两遍(左边声明 + 右边 new)
ArrayList<String> list = new ArrayList<String>();
Map<String, List<Integer>> map = new HashMap<String, List<Integer>>();

// var 写法:类型只在右边出现一次,编译器从右边推断左边
var list = new ArrayList<String>();       // 推断为 ArrayList<String>
var map = new HashMap<String, List<Integer>>();  // 推断为 HashMap<...>

关键:var 不改变类型系统

var count = 10;        // 推断为 int
count = "hello";       // ✗ 编译错误!count 是 int,不能赋字符串
                       // → 证明 var 是静态类型,类型确定后不能变

var 的使用限制(只能用在「能推断出类型」的局部变量):

能用 var不能用 var
局部变量(有初始化值)成员变量(字段)
for 循环变量方法参数
try-with-resources方法返回类型
没有初始化值的变量(var x; ✗,推断不出类型)
初始化为 null(var x = null; ✗,推断不出具体类型)
Lambda 表达式(var f = () -> {} ✗,无目标类型无法推断)

⚠️ var保留类型名(reserved type name),不是关键字——所以它不影响已有代码里把 var 当变量名/方法名的情况(向后兼容)。但你不能用 var 作为类名或接口名。这个设计是为了不破坏 Java 10 之前可能已经用 var 当标识符的老代码。

完整版教学

一、var 解决什么:消除重复的类型书写

Java 是静态类型语言,声明变量要写类型。但很多时候类型信息「重复」了——左边声明写一遍、右边 new 又写一遍,尤其泛型嵌套时冗长:

// 类型信息重复且冗长
Map<String, List<CustomerOrder>> ordersByCustomer = new HashMap<String, List<CustomerOrder>>();
//  ↑ 左边一长串                                        ↑ 右边又一长串,几乎一样

// var:右边已经说清类型了,左边不用再写一遍
var ordersByCustomer = new HashMap<String, List<CustomerOrder>>();

var 的价值就是消除这种冗余——当右边的表达式已经明确表达了类型,左边就不必重复。这不是「偷懒」,而是减少视觉噪音、提升可读性(尤其泛型嵌套、类型名很长时)。它借鉴了其他语言(C# 的 var、C++ 的 auto、Kotlin/Scala 的类型推断)的成熟做法。理解「var 是消除重复类型书写的语法糖」,就抓住了它的定位——它是便利性特性,不改变语言的类型本质。

二、核心澄清:var 不是动态类型

这是关于 var 最大的误解,必须讲清。var 不会让 Java 变成 JavaScript/Python 那样的动态类型语言

动态类型(如 JS):变量类型在运行时决定,可以随时变
  let x = 10;      // x 是数字
  x = "hello";     // 合法!x 现在是字符串(运行时类型可变)

Java 的 var(静态类型):类型在编译期由编译器推断并"焊死"
  var x = 10;      // 编译器推断 x 是 int,之后 x 永远是 int
  x = "hello";     // ✗ 编译错误!int 不能赋字符串

关键区别:动态类型是「变量本身没有固定类型」,var 是「编译器帮你推断出固定类型」。用了 var 之后,变量的类型和你手写类型完全一样、一样严格——只是「谁来写这个类型」的区别(编译器写 vs 你写)。所以:Java 用了 var 仍然是 100% 的静态强类型语言,编译期该做的类型检查一个不少。这是面试最爱考的点——「var 是不是让 Java 变弱类型了」,答案是坚决的「不是」。

三、类型推断在编译期完成

var 的类型推断发生在编译期,编译后字节码里根本没有 var——它已经被替换成推断出的具体类型:

源码:       var list = new ArrayList<String>();
             ↓ 编译器推断
字节码等价于:ArrayList<String> list = new ArrayList<String>();
             (字节码里是具体类型,没有 var 的痕迹)

所以 var 是纯粹的编译期语法糖——它在编译时就被「翻译」成了具体类型,对运行时零影响、零开销(不像反射那样有运行时成本)。这也说明为什么 var 必须能「推断出类型」:编译器要在编译期就确定它是什么类型,才能替换。如果推断不出来(如 var x = null 没有类型信息、var x; 没有初始值),编译器无从推断,直接报错。理解「var 编译期就变成了具体类型」,就理解了它的所有限制的来源。

四、为什么有这些使用限制

var 的限制都源于一个原则:必须能在编译期从「初始化表达式」推断出唯一确定的类型。凡是推断不出类型的场景,都不能用:

var x;                       // ✗ 没有初始值,推断不出类型
var y = null;                // ✗ null 没有类型信息,推断不出具体类型
var f = () -> System.out.println("hi");  // ✗ Lambda 需要目标类型,var 提供不了
var arr = {1, 2, 3};         // ✗ 数组初始化器需要目标类型

成员变量、方法参数、方法返回类型不能用 var,原因不同——它们是「接口契约」的一部分,需要明确的类型来定义 API:

成员变量、方法参数、返回类型:
  它们是类/方法对外的"契约",类型必须明确写出来(供调用者、其他类看)
  如果用 var,API 的类型就模糊了,破坏了封装和可读性
  所以 Java 只允许 var 用在"局部变量"(方法内部的临时变量,作用域小、影响局部)

核心逻辑:var 只用于局部变量(作用域小、影响范围有限、类型能从初始化推断),不用于「对外契约」(字段、参数、返回值需要明确类型)。这个限制是有意的设计——把便利性限制在「不影响 API 清晰度」的范围内。

五、什么时候该用 var,什么时候不该

var 用得好提升可读性,用得滥反而降低可读性。原则是「右边类型一目了然时用,看不出类型时别用」:

// ✓ 推荐用 var:右边明确表达了类型,且类型名冗长
var users = new ArrayList<User>();              // 一眼知道是 ArrayList<User>
var entry = map.entrySet().iterator().next();   // 类型很长,var 更清爽

// ✗ 不推荐用 var:右边看不出具体类型,降低可读性
var result = service.process();    // process() 返回什么?看不出来,读者要跳去看方法
var data = getData();              // data 是什么类型?不清楚
var x = 1;                         // int 就一个字,写 int x = 1 更清楚

判断标准:读者能不能一眼看出变量的类型。如果右边是 new XxxList<>() 这种显式构造,类型清清楚楚,用 var 简洁;如果右边是方法调用(返回类型不直观),用 var 会让读者「不知道这是什么类型」,反而要跳去看方法签名,降低可读性。所以 var 的黄金法则是:用它来省略「显而易见的类型」,而不是「隐藏不明显的类型」。滥用 var 让所有变量都失去类型标注,是反模式。

六、var 与泛型、菱形操作符的配合

var 和泛型一起用时有个细节要注意——类型信息必须在右边给全

var list = new ArrayList<String>();   // ✓ 右边写全泛型 → 推断为 ArrayList<String>
var list = new ArrayList<>();         // ⚠️ 右边是菱形<>,无上下文 → 推断为 ArrayList<Object>!
// 因为 var 时左边没有类型,菱形操作符<>没有可推断的目标 → 退化成 Object

// 对比传统写法:
List<String> list = new ArrayList<>();  // ✓ 左边有类型,菱形<>能推断出 String

坑点:var + 菱形操作符 <> 会让泛型退化成 Object——因为菱形 <> 依赖「左边的类型」来推断泛型,而 var 时左边没有类型可依赖,所以 <> 就推断成 Object 了。这和平时「List<String> list = new ArrayList<>()」不同(那时左边有 List<String> 给菱形提供推断依据)。所以用 var 时,泛型要在右边写全new ArrayList<String>() 而非 new ArrayList<>()),否则会丢失泛型信息。这是 var 使用中最隐蔽的坑。

记忆钩子:「var 是局部变量类型推断(Java 10)——编译器从右边推断类型,纯编译期语法糖、零运行时开销;不是动态/弱类型,类型编译期焊死不能变,Java 仍是静态强类型;只用于局部变量(不能用于字段/参数/返回值/无初值/null/Lambda);用它省显而易见的类型、别隐藏不明显的;var+菱形<>会退化成 Object」

七、常见误区与追问

  • 误区:var 让 Java 变成了动态类型语言。 不是——var 的类型在编译期被推断并焊死、之后不能变(var x=10; x="hi" 编译报错),Java 仍是 100% 静态强类型;var 只是省略手写类型的语法糖。
  • 误区:var 有运行时开销或影响性能。 零开销——它是纯编译期语法糖,编译后字节码里是具体类型、没有 var 的痕迹,对运行时无影响。
  • 误区:var 可以用在任何地方。 只能用于有初始化值的局部变量;不能用于成员变量、方法参数、返回类型、无初值变量、var x=null、Lambda。
  • 误区:var 应该到处用让代码更简洁。 滥用会降低可读性——右边看不出类型时(如方法调用返回值)用 var 让读者不知道类型;应只用于「类型显而易见」的场景。
  • 追问:var 是关键字吗? 不是,是「保留类型名」——不影响已有代码把 var 当变量名/方法名(向后兼容),但不能用作类名/接口名。
  • 追问:var list = new ArrayList<>() 推断成什么类型? ArrayList<Object>!因为菱形 <> 依赖左边类型推断泛型,var 时左边无类型可依赖,退化成 Object;用 var 时泛型要在右边写全。
  • 追问:为什么成员变量不能用 var? 成员变量是类对外的契约,类型需明确写出供其他类/调用者看;var 会模糊 API 类型、破坏可读性,所以只限局部变量(作用域小、影响局部)。

八、加强记忆

var(Java 10)是局部变量的类型推断——编译器根据右边的初始化表达式自动推断左边变量的类型,省去重复书写(尤其泛型嵌套冗长时如 Map<String,List<Integer>>)。最核心的澄清:它不是动态类型、不是弱类型——var 的类型在编译期就被推断并焊死、之后不能改var x=10; x="hi" 编译报错),所以 Java 用了 var 仍是 100% 静态强类型语言,类型检查一个不少。它是纯编译期语法糖(字节码里是具体类型、零运行时开销)。限制都源于「必须能从初始化推断出类型」:只用于有初值的局部变量,不能用于字段、方法参数、返回类型(这些是对外契约需明确类型)、无初值、null、Lambda。使用原则:用它省「显而易见的类型」(右边 new XxxList<>),别隐藏「不明显的类型」(方法返回值),否则降低可读性。最隐蔽的坑:var list = new ArrayList<>() 会因菱形 <> 无左边类型可推断而退化成 ArrayList<Object>,所以用 var 时泛型要在右边写全。一句话「var 是编译期局部变量类型推断、不是动态类型(类型焊死不变)、只用于局部变量、省显而易见的类型、菱形要写全」。