2.1 变量、赋值与作用域
前置知识:完成第 1 章
预计时间:25 分钟
练习环境:JDK 17+ 与 Bash;示例写入临时目录
完成标志:能声明、初始化、修改并读取局部变量
上一章一直输出固定文字。这一页用变量保存分数,再把它从 95 改为 98。
变量是一个语言层面的名字
初学时可以把变量想成带标签的盒子,但别把类比当成 JVM 的物理图纸。编译器和运行时可能把局部变量放进栈帧槽、寄存器,甚至优化掉。Java 代码真正承诺的是:
- 变量有声明的类型;
- 在作用域内可以通过名字读取或赋值;
- 读取时得到该变量当前保存的值。
先创建隔离目录,再新建 VariablesDemo.java:
chapter2_workdir=$(mktemp -d)
readonly chapter2_workdir
cd "$chapter2_workdir"public class VariablesDemo {
public static void main(String[] args) {
int score = 95;
System.out.println(score);
score = 98;
System.out.println(score);
}
}mkdir -p out
javac -d out VariablesDemo.java
java -cp out VariablesDemo预期输出:
95
98int score = 95; 同时完成三件事:
| 部分 | 含义 |
|---|---|
int | 声明类型为 32 位有符号整数 |
score | 声明变量名 |
= 95 | 把整数值 95 赋给变量 |
第二次写 score = 98; 是赋新值,不是重新声明。若再写 int score = 98;,会在同一作用域中形成重复局部变量错误。
声明、初始化、赋值
三个词不要混用:
int count; // 声明
count = 1; // 第一次赋值,也完成初始化
count = 2; // 再次赋值局部变量没有自动可读的默认值。编译器进行“明确赋值”检查:
int count;
System.out.println(count); // 编译错误:可能尚未初始化类的字段和新数组中的元素会获得类型默认值,但那是另一条规则。不要把字段默认值套到方法局部变量上。
静态类型不等于值不能变化
int level = 1;
level = 2; // 合法:值变了,类型没变
// level = "two"; // 编译错误:String 不能赋给 intJava 是静态类型语言:编译器在运行前检查表达式类型,局部变量 level 的类型不会因后续赋值而变成 String。静态类型并不意味着所有错误都能在编译期发现,也不意味着变量的值不可修改。
Python 的常见写法不同:
score = 95
score = "excellent"Python 名字可以先后绑定到不同类型的对象,类型不兼容通常在相关操作执行时暴露。两种模型各有工具支持和设计取舍,不宜简单归纳为“灵活”或“严谨”。
作用域决定名字在哪里可见
局部变量从声明处开始,在所属代码块内可见:
int outer = 1;
if (outer > 0) {
int inner = 2;
System.out.println(outer + inner);
}
System.out.println(outer);
// System.out.println(inner); // 编译错误:inner 已离开作用域缩小作用域能减少误用。只在一个 if 块里需要的数据,就在那个块里声明。
八种基本类型先认名字
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 很常用,但不是基本类型:
String travelerName = "Lin";具体范围、浮点误差和 char 的 Unicode 边界留到下一讲。当前先掌握声明形状与常用选择:普通整数优先 int,普通浮点计算优先 double,真假条件使用 boolean。
var 是局部变量类型推断
Java 10 起,可以让编译器从初始化表达式推断局部变量类型:
var name = "Alice"; // String
var count = 42; // int
var rate = 0.5; // double推断发生在编译期,结果仍是确定的静态类型:
var count = 42;
// count = "forty-two"; // 编译错误常见限制:
// var missing; // 没有初始化式,无法推断
// var unknown = null; // 单独的 null 没有可推断目标类型var 主要用于方法内部的局部变量,也可出现在增强 for 循环和部分资源变量位置;不能替代字段类型、方法参数类型或返回类型。类型一眼可见时显式写出通常更适合教程和公共 API。
final 表示只能赋值一次
final int maxAttempts = 3;
// maxAttempts = 4; // 编译错误空白 final 局部变量也可以稍后完成唯一一次赋值,只要编译器能证明所有读取路径都已初始化:
final int port;
port = 8080;final 引用不能改指向,但对象本身未必不可变。后面学对象时再展开。
命名让读者看懂数据角色
int retryCount = 0;
boolean connectionReady = false;
String displayName = "Lin";- 变量和方法通常用小驼峰;
- 类和接口通常用大驼峰;
- 常量通常用大写下划线,如
MAX_RETRY_COUNT; - 避免
l、O这类容易与数字混淆的单字母名字; - 名字描述业务含义,不重复类型,如
age比intAge自然。
专业速查
| 问题 | 判断 |
|---|---|
| 局部变量未初始化能读吗 | 不能,编译器执行明确赋值检查 |
| 变量能换类型吗 | 声明后的静态类型不变 |
var 是动态类型吗 | 不是,编译期推断出固定类型 |
String 是基本类型吗 | 不是,是引用类型 |
| 名字何时可见 | 从声明处到所属作用域结束,受遮蔽等规则约束 |
final 对引用意味着什么 | 引用不能重新赋值,不保证对象不可变 |
离开这一页前
确认自己能区分声明、初始化和再次赋值,知道局部变量为何不能在赋值前读取,也能解释 var 仍是静态类型。然后清理本页文件:
cd
ls -ld -- "$chapter2_workdir"
rm -r -- "$chapter2_workdir"“盒子”帮助理解赋值,但不承诺某个内存地址。接下来检查这些值能装多大、什么时候会丢失精度。
规范入口:JLS §4.12:变量、§14.4:局部变量声明、§16:明确赋值。