跳到内容

第 1 卷 / 第 2 章 / 第 1 课

2.1 变量、赋值与作用域 ​

前置知识:完成第 1 章

预计时间:25 分钟

练习环境:JDK 17+ 与 Bash;示例写入临时目录

完成标志:能声明、初始化、修改并读取局部变量

上一章一直输出固定文字。这一页用变量保存分数,再把它从 95 改为 98。

变量是一个语言层面的名字 ​

初学时可以把变量想成带标签的盒子,但别把类比当成 JVM 的物理图纸。编译器和运行时可能把局部变量放进栈帧槽、寄存器,甚至优化掉。Java 代码真正承诺的是:

  • 变量有声明的类型;
  • 在作用域内可以通过名字读取或赋值;
  • 读取时得到该变量当前保存的值。

先创建隔离目录,再新建 VariablesDemo.java:

bash
chapter2_workdir=$(mktemp -d)
readonly chapter2_workdir
cd "$chapter2_workdir"
java
public class VariablesDemo {
    public static void main(String[] args) {
        int score = 95;
        System.out.println(score);

        score = 98;
        System.out.println(score);
    }
}
bash
mkdir -p out
javac -d out VariablesDemo.java
java -cp out VariablesDemo

预期输出:

text
95
98

int score = 95; 同时完成三件事:

部分含义
int声明类型为 32 位有符号整数
score声明变量名
= 95把整数值 95 赋给变量

第二次写 score = 98; 是赋新值,不是重新声明。若再写 int score = 98;,会在同一作用域中形成重复局部变量错误。

声明、初始化、赋值 ​

三个词不要混用:

java
int count;      // 声明
count = 1;      // 第一次赋值,也完成初始化
count = 2;      // 再次赋值

局部变量没有自动可读的默认值。编译器进行“明确赋值”检查:

java
int count;
System.out.println(count); // 编译错误:可能尚未初始化

类的字段和新数组中的元素会获得类型默认值,但那是另一条规则。不要把字段默认值套到方法局部变量上。

静态类型不等于值不能变化 ​

java
int level = 1;
level = 2;       // 合法:值变了,类型没变
// level = "two"; // 编译错误:String 不能赋给 int

Java 是静态类型语言:编译器在运行前检查表达式类型,局部变量 level 的类型不会因后续赋值而变成 String。静态类型并不意味着所有错误都能在编译期发现,也不意味着变量的值不可修改。

Python 的常见写法不同:

python
score = 95
score = "excellent"

Python 名字可以先后绑定到不同类型的对象,类型不兼容通常在相关操作执行时暴露。两种模型各有工具支持和设计取舍,不宜简单归纳为“灵活”或“严谨”。

作用域决定名字在哪里可见 ​

局部变量从声明处开始,在所属代码块内可见:

java
int outer = 1;

if (outer > 0) {
    int inner = 2;
    System.out.println(outer + inner);
}

System.out.println(outer);
// System.out.println(inner); // 编译错误:inner 已离开作用域

缩小作用域能减少误用。只在一个 if 块里需要的数据,就在那个块里声明。

八种基本类型先认名字 ​

java
byte small = 100;
short port = 8080;
int population = 1_000_000;
long distance = 3_000_000_000L;
float ratio = 0.5F;
double temperature = 21.5;
char grade = 'A';
boolean ready = true;

String 很常用,但不是基本类型:

java
String travelerName = "Lin";

具体范围、浮点误差和 char 的 Unicode 边界留到下一讲。当前先掌握声明形状与常用选择:普通整数优先 int,普通浮点计算优先 double,真假条件使用 boolean。

var 是局部变量类型推断 ​

Java 10 起,可以让编译器从初始化表达式推断局部变量类型:

java
var name = "Alice"; // String
var count = 42;      // int
var rate = 0.5;      // double

推断发生在编译期,结果仍是确定的静态类型:

java
var count = 42;
// count = "forty-two"; // 编译错误

常见限制:

java
// var missing;        // 没有初始化式,无法推断
// var unknown = null; // 单独的 null 没有可推断目标类型

var 主要用于方法内部的局部变量,也可出现在增强 for 循环和部分资源变量位置;不能替代字段类型、方法参数类型或返回类型。类型一眼可见时显式写出通常更适合教程和公共 API。

final 表示只能赋值一次 ​

java
final int maxAttempts = 3;
// maxAttempts = 4; // 编译错误

空白 final 局部变量也可以稍后完成唯一一次赋值,只要编译器能证明所有读取路径都已初始化:

java
final int port;
port = 8080;

final 引用不能改指向,但对象本身未必不可变。后面学对象时再展开。

命名让读者看懂数据角色 ​

java
int retryCount = 0;
boolean connectionReady = false;
String displayName = "Lin";
  • 变量和方法通常用小驼峰;
  • 类和接口通常用大驼峰;
  • 常量通常用大写下划线,如 MAX_RETRY_COUNT;
  • 避免 l、O 这类容易与数字混淆的单字母名字;
  • 名字描述业务含义,不重复类型,如 age 比 intAge 自然。

专业速查 ​

问题判断
局部变量未初始化能读吗不能,编译器执行明确赋值检查
变量能换类型吗声明后的静态类型不变
var 是动态类型吗不是,编译期推断出固定类型
String 是基本类型吗不是,是引用类型
名字何时可见从声明处到所属作用域结束,受遮蔽等规则约束
final 对引用意味着什么引用不能重新赋值,不保证对象不可变

离开这一页前 ​

确认自己能区分声明、初始化和再次赋值,知道局部变量为何不能在赋值前读取,也能解释 var 仍是静态类型。然后清理本页文件:

bash
cd
ls -ld -- "$chapter2_workdir"
rm -r -- "$chapter2_workdir"

“盒子”帮助理解赋值,但不承诺某个内存地址。接下来检查这些值能装多大、什么时候会丢失精度。

→ 下一步:基本类型的范围与边界

规范入口:JLS §4.12:变量、§14.4:局部变量声明、§16:明确赋值。

Built with VitePress | Software Systems Atlas