变量与常量
本教程共 100 篇 · 第 12 篇 · 更新于 2026-08-05 · 约 6 分钟阅读
12. 变量与常量
本节目标:理解变量就是「带名字的内存格子」,分清局部、成员、静态三类变量的区别,学会用
final定义常量,并了解var这种偷懒写法能用在哪里。
变量到底是什么
你可以把变量想象成宾馆里的一个房间:类型决定房间能住什么人(数字还是字符),名字是房间号,值是住在里面的人。
住客能换——这就是「变」量。你今天让 age 等于 18,明天可以改成 19。
int age = 18;
age = 19; // 合法,变量的值可以改
```java
声明变量时,`类型 名字 = 初始值;` 是一条完整语句。等号在这里叫「赋值」,意思是把右边的值放进左边的变量。
声明和赋值也可以分开:
```java
int score; // 先声明
score = 95; // 再赋值
不过日常更推荐声明时直接给初值,少一步出错的机会。
三类变量
同样是变量,放在不同位置,待遇完全不同。主要分三类。
局部变量
写在方法里面的变量,叫局部变量。它的作用范围仅限于那个方法。
public void test() {
int count = 0; // 局部变量
count++;
}
```bash
局部变量**没有默认值**,用之前必须自己赋值,否则编译报错。这是上一章提过的小差别。
### 成员变量(实例变量)
写在**类里面、方法外面**的变量,叫成员变量,也叫实例变量。每个对象都有自己的一份。
```java
public class User {
String name; // 成员变量
int age; // 成员变量
}
成员变量有默认值(数字 0、boolean 是 false、引用类型是 null),不赋值也能用。
静态变量(类变量)
在成员变量前加 static,它就属于「类」而不是「某个对象」,所有对象共享同一份。
public class Config {
static String appName = "MyApp"; // 静态变量,属于类
}
```bash
静态变量通过 `类名.变量名` 访问,不需要先 `new` 对象。具体的 `static` 含义后面会单独讲,这里先知道有这回事。
## 常量:一次赋值,永不改变
有些值你希望它固定不变,比如圆周率、最大重试次数。这时用 `final` 修饰:
```java
final double PI = 3.141592653589793;
PI = 3.14; // 编译报错:final 变量不能重新赋值
编译时的报错长这样,见到就知道是动了常量:
error: cannot assign a value to final variable PI
PI = 3.14;
^
1 error
```java
`final` 变量叫常量,特点是**只能赋值一次**。常量名按约定全大写、下划线分隔,一眼就能认出。
常见写法是配上 `static`,变成全类共享的常量:
```java
public class MathConst {
public static final double PI = 3.141592653589793;
}
Note
final只保证「引用不变」。对基本类型,值当然不变;对引用类型(比如数组、对象),final锁住的是「指向哪个对象」,对象里面的内容还是能改的。这块到后面讲对象时再体会。
var:能省则省的类型推断
从 Java 10 开始,局部变量可以用 var 代替具体类型:
var name = "Tom"; // 编译器推断为 String
var price = 19.9; // 推断为 double
var list = new ArrayList<String>(); // 推断为 ArrayList<String>
```bash
右边写了值,编译器自己就能猜出类型,左边就不必重复写了。`var` 本质是「语法糖」,编译后和写全类型没区别。
但有边界,别乱用:
- **只能用在局部变量**,不能用于字段、方法参数、返回值。
- **声明时必须初始化**,因为类型靠右边的值推出来。
- 推出来是什么类型,就定死了,后面不能塞别的类型。
```java
var x = 10;
// x = "hello"; // 报错:x 已被推断为 int
Warning
var虽爽,但别在类型不明显的地方硬用。比如var result = compute();——读代码的人得跳进compute()才知道result是啥。可读性优先时,老老实实写类型名。
var 能用在哪,一张表钉死
| 场景 | 能否用 var | 说明 |
|---|---|---|
| 方法内的局部变量 | 能 | 主战场 |
for 循环计数器 | 能 | for (var i = 0; i < 10; i++) |
增强 for 的元素 | 能 | for (var s : list) |
| try-with-resources 的资源变量 | 能 | Java 10 起支持 |
| 成员变量、静态变量 | 不能 | 编译报错 |
| 方法参数、返回值类型 | 不能 | 编译报错 |
| 只声明不赋值 | 不能 | 没值就推不出类型 |
初值写 null | 不能 | null 什么类型都可能是 |
最后两条的报错很好认:
error: cannot infer type for local variable x
(variable initializer is 'null')
```bash
Java 10 之前没有 `var`,所有局部变量都得写全类型名,像 `HashMap<String, List<Integer>> map = new HashMap<>();` 这种左右重复一遍的写法很常见。那属于历史兼容写法,今天在 Java 25 LTS 下写新代码,右边类型已经写明白时用 `var` 更清爽。
### final 不一定要在声明时赋值
常量不一定非得在声明那一行给值。只要保证「赋值只发生一次」就合法:
```java
final int maxRetry;
if (someCondition) {
maxRetry = 3;
} else {
maxRetry = 5;
}
// maxRetry = 10; // 报错:已经赋过值了
这种写法在「值要按情况决定,但定下后不再改」的场景里很常见。
变量作用域初探
变量的「作用域」指它能在哪些范围内被访问。一条朴素规则:变量只在声明它的那对花括号 { } 内有效。
public void demo() {
int x = 1; // x 在整个 demo 方法里可见
if (true) {
int y = 2; // y 只在 if 这个块里可见
}
// System.out.println(y); // 报错:y 已经出了作用域
}
```bash
作用域到面向对象部分会讲得更透,先建立「出了那对花括号,里面的局部变量就失效」这个直觉。
## 命名要见名知意
变量名也是标识符,遵守上一章的规则和约定:小驼峰、`$`/`_` 不开头、不用关键字。
更重要的是起得明白。`int n;` 能跑,但 `int studentCount;` 让人秒懂。常量同理:`MAX_RETRY` 比 `m` 强一百倍。
## 变量与常量,一张表理清
把本章核心放在一起对比,方便回看:
| 概念 | 写法 | 能否改值 | 典型位置 |
|------|------|----------|----------|
| 局部变量 | `int x = 1;` | 能 | 方法内部 |
| 成员变量 | `int x;` | 能 | 类里、方法外 |
| 静态变量 | `static int x;` | 能(类级别) | 类里、方法外 |
| 常量 | `final int X = 1;` | 不能 | 任意 |
一句话区分:**变量的值会变,常量的值定死;局部变量管方法内,成员变量归对象,静态变量归整个类。**
## 常见疑问
**Q:用了 `var`,Java 是不是变成弱类型语言了?**
不是。类型在编译那一刻就被推断出来并钉死,运行期和写全类型名的代码一模一样。少敲的只是字,类型检查一点没少。
**Q:局部变量忘了赋值直接用,会怎样?**
编译阶段就被拦住,跑都跑不起来:
```text
error: variable score might not have been initialized
这比运行时才炸要友好得多。成员变量有默认值所以不会报这个错,但也别依赖默认值写逻辑,容易出意料之外的结果。
Q:常量是不是必须写成 static final?
看共享范围。方法里临时用一次的固定值,final int MAX = 10; 就够。要让整个类甚至外部都能读同一份,才用 public static final。
小结
变量是带名字的内存格子,值可以改。声明格式:类型 名字 = 值;。
三类变量:局部变量(方法内,无默认必须初始化)、成员变量(类内方法外,有默认)、静态变量(static,属于类)。
final 定义常量,只能赋值一次,命名全大写下划线。var(Java 10)可做局部变量类型推断,但必须初始化、不能用于字段,类型推断后锁定。