变量概述
本章定位:理解Java变量的本质——内存中的一块命名存储空间。掌握标识符的命名规则与规范、变量的声明语法与作用域三大基石,以及常量的定义与使用。白歌说:"变量名是你写给人看的,编译器只看类型;常量和标识符规范是你写给自己看的,三个月后维护代码时你会感谢现在的自己。"
什么是变量
变量(Variable) 是内存中一块被命名的存储区域,用于存放特定类型的数据。Java是强类型语言,每个变量在声明时必须指定类型,且该类型在变量的整个生命周期中不可改变。
变量的三要素:
- 类型(Type):决定内存占用大小和数据范围
- 名称(Name):程序中对这块内存的标识符
- 值(Value):实际存储的数据
变量声明语法
| 声明形式 | 语法格式 | 示例 |
|---|---|---|
| 声明 + 赋值(两步) | 类型 变量名; → 变量名 = 值; | int count; → count = 100; |
| 声明并初始化(一步) | 类型 变量名 = 值; | int count = 100; |
| 多个同类型变量 | 类型 变量1 = 值1, 变量2 = 值2; | int a = 1, b = 2; |
| 常量(不可变) | final 类型 变量名 = 值; | final double PI = 3.141592653589793; |
完整示例
/**
* 飞翔科技 - 变量声明演示
* 场景:白歌教小崔规范地声明购物车变量
*/
public class VariableDeclaration {
public static void main(String[] args) {
// 方式一:先声明后赋值
float price;
price = 188.88f;
// 方式二:声明并初始化(推荐)
int quantity = 1000;
// 方式三:多个同类型变量同时声明
float discount = 88.8f, shippingFee = 8.8f;
// 常量:使用 final 修饰
final String SHOP_NAME = "飞翔小店";
// 变量参与运算
float totalAmount = price * 2 + shippingFee;
System.out.println("【" + SHOP_NAME + "】");
System.out.println("单价:¥" + price);
System.out.println("库存:" + quantity + "件");
System.out.println("折扣价:¥" + discount);
System.out.println("运费:¥" + shippingFee);
System.out.println("购买2件总金额:¥" + totalAmount);
}
}
运行输出:
【飞翔小店】
单价:¥188.88
库存:1000件
折扣价:¥88.8
运费:¥8.8
购买2件总金额:¥386.56
标识符(Identifier)
标识符是程序员为类、方法、变量、常量等元素起的名字。在 Java 中,标识符不是随心所欲的——它必须遵循严格的命名规则,并鼓励遵守行业命名规范。
标识符命名规则(硬性规则,编译期检查)
| 规则 | 正确示例 | 错误示例 | 错误原因 |
|---|---|---|---|
只能包含字母、数字、下划线_、美元符$ | basicSalary、orderId | basic-Salary、中文名 | 含非法字符 - 或非 ASCII 字符(允许但强烈不推荐) |
| 不能以数字开头 | orderId2025 | 2025OrderId | 以数字开头 |
| 不能使用关键字/保留字 | myClass | class、int、if | class、int、if 等是 Java 关键字 |
| 区分大小写 | price ≠ Price | — | price、Price、PRICE 是三个不同标识符 |
| 不能包含空格 | basicSalary | basic Salary | 含空格 |
| 长度无限制 | thisIsAVeryLongVariableName | — | 理论上无上限,但建议适度 |
Java 关键字与保留字(53个)
Java 共有 53个关键字和保留字,这些单词不能用作标识符:
数据类型(8):
byteshortintlongfloatdoublecharboolean流程控制(10):ifelseswitchcasedefaultforwhiledobreakcontinue修饰符(12):publicprotectedprivatestaticfinalabstractsynchronizedtransientvolatilestrictfpnativedefault(接口默认方法,JDK 8+) 异常处理(6):trycatchfinallythrowthrowsassert类与对象(10):classinterfaceenumextendsimplementsnewthissuperinstanceofpackage其他与保留字(7):returnvoidimportconst(保留)goto(保留)_(JDK 9+ 关键字)var(JDK 10+ 局部变量类型推断)record(JDK 16+)sealedpermits(JDK 17+)
📝 记忆技巧:IDE 通常会对关键字高亮显示,在 Eclipse/IntelliJ IDEA 中如果标识符变蓝/变紫,说明它可能是关键字。
标识符命名规范(软性规范,团队约定)
| 规范 | 命名法 | 适用场景 | 示例 |
|---|---|---|---|
| 大驼峰(PascalCase) | 每个单词首字母大写 | 类名、接口名、枚举名 | StudentService、OrderDao、HttpServletRequest |
| 小驼峰(camelCase) | 首字母小写,后续单词首字母大写 | 变量名、方法名、参数名 | basicSalary、orderId、getUserName() |
| 全大写下划线(UPPER_SNAKE_CASE) | 全大写,单词间用下划线分隔 | 常量(final 修饰的变量) | MAX_VALUE、DEFAULT_PORT、PI |
| 全小写点分隔 | 全小写,层级用点分隔 | 包名 | com.feixiangtech.order |
| 见名知意 | 避免单字母(循环变量除外)、不用拼音 | 所有命名 | quantity 而非 q;price 而非 jiage |
// ✅ 飞翔科技 — 标识符命名规范演示
public class StudentScoreCalculator { // 类名:大驼峰
public static final int MAX_SCORE = 100; // 常量:全大写下划线
private String studentName; // 变量名:小驼峰
private int mathScore; // 变量名:小驼峰
public int calculateTotalScore() { // 方法名:小驼峰
int totalScore = 0; // 局部变量:小驼峰
return totalScore;
}
}
命名规则
Java的标识符命名遵循以下规则。白歌在Code Review时最常抓的问题就是命名不规范。
硬性规则(编译期检查)
| 规则 | 正确示例 | 错误示例 | 错误原因 |
|---|---|---|---|
只能包含字母、数字、下划线_、美元符$ | basicSalary | basic-Salary | 含非法字符- |
| 不能以数字开头 | orderId2025 | 2025OrderId | 以数字开头 |
| 不能使用关键字/保留字 | myClass | class | class是关键字 |
| 区分大小写 | price ≠ Price | — | — |
| 不能包含空格 | basicSalary | basic Salary | 含空格 |
软性规范(团队约定)
| 规范 | 说明 | 示例 |
|---|---|---|
| 驼峰命名(camelCase) | 变量名/方法名,首字母小写 | basicSalary, orderId |
| 帕斯卡命名(PascalCase) | 类名/接口名,首字母大写 | StudentService, OrderDao |
| 全大写下划线 | 常量 | MAX_VALUE, DEFAULT_PORT |
| 见名知意 | 避免单字母(循环变量除外) | quantity 而非 q |
| 不用拼音 | 国际化与可读性 | price 而非 jiage |
变量分类与作用域
作用域详细示例
/**
* 飞翔科技 - 变量作用域演示
* 场景:大翔要小崔理解工资计算中各变量的生命周期
*/
public class SalaryCalculator {
// 实例变量:属于对象,有默认值
String employeeName; // 默认 null
float basicSalary; // 默认 0.0f
// 静态变量:属于类,全局共享
static String companyName = "飞翔科技";
static final float TAX_RATE = 0.1f; // 静态常量
public void calculateSalary(float bonus) {
// 局部变量:必须显式初始化
float socialSecurity = 2320.00f;
float personalTax = basicSalary * TAX_RATE;
float actualSalary = basicSalary + bonus - socialSecurity - personalTax;
// bonus 是方法参数,也是局部变量
System.out.println("实发工资:¥" + actualSalary);
}
public static void main(String[] args) {
SalaryCalculator sc = new SalaryCalculator();
sc.employeeName = "大翔";
sc.basicSalary = 8888.00f;
sc.calculateSalary(18888.00f); // 绩效奖金作为参数传入
// 静态变量通过类名访问
System.out.println("公司:" + SalaryCalculator.companyName);
}
}
运行输出:
实发工资:¥24377.20
公司:飞翔科技
局部变量的"先声明后使用"陷阱
// ❌ 错误:局部变量未初始化就使用
public void wrongUsage() {
int quantity;
System.out.println(quantity); // 编译错误:variable quantity might not have been initialized
}
// ✅ 正确:声明时初始化
public void correctUsage() {
int quantity = 1000;
System.out.println(quantity); // 输出 1000
}
原理:实例变量和静态变量有JVM自动赋予的默认值(int默认为0,float默认为0.0f,boolean默认为false,引用类型默认为null),但局部变量存放在栈中,JVM不会自动初始化,编译器强制要求"先赋值后使用",以防止读取垃圾数据。
常量(Constant)
常量是用 final 关键字修饰的变量,一旦赋值后不可更改。常量的核心价值在于:避免魔术数字(Magic Number)、提高代码可读性和可维护性、编译器可进行内联优化。
常量的两种定义方式
| 类型 | 语法 | 特点 | 示例 |
|---|---|---|---|
| final 变量(运行时常量) | final 类型 变量名 = 值; | 每个对象有一份副本,值在运行时确定 | final double pi = 3.141592653589793; |
| static final 变量(编译时常量) | static final 类型 变量名 = 值; | 类级别共享,编译时确定值,编译器会直接内联到使用处 | static final int MAX_SIZE = 100; |
编译时常量(Compile-Time Constant)
当 static final 修饰的变量满足以下条件时,称为编译时常量:
- 基本类型或 String 类型
- 声明时直接赋值(不是通过方法调用)
- 赋值表达式在编译期可确定
// ✅ 编译时常量:编译器可将 MAX_SIZE 直接内联为 100
public static final int MAX_SIZE = 100;
// ✅ 编译时常量:字符串拼接在编译期确定
public static final String FULL_NAME = "飞翔" + "科技";
// ❌ 不是编译时常量:new 运算符,值在运行时确定
public static final String CREATE_TIME = new String("2025-01-01");
// ❌ 不是编译时常量:方法调用,值在运行时确定
public static final long CURRENT_TIME = System.currentTimeMillis();
📌 编译时常量的优化:编译时常量会被直接替换为字面量内联到字节码中。如果你修改了一个编译时常量的值,所有引用该常量的类都必须重新编译,否则会继续使用旧值。这是 Java 中著名的"常量反模式"。
常量命名规范
| 规则 | 示例 |
|---|---|
| 全大写字母 | MAX_VALUE 而非 maxValue |
| 单词间用下划线分隔 | DEFAULT_PORT_NUMBER |
| 含义明确 | SECONDS_PER_HOUR 而非 S |
| 放置位置 | 通常放在类的顶部(成员变量区域) |
常量的意义(为什么用常量而不是直接写值?)
// ❌ 反例:魔术数字(Magic Number),难以理解和维护
if (userLevel > 3) {
discount = price * 0.8;
}
// ✅ 正例:用常量替代,代码一目了然
public static final int VIP_LEVEL_THRESHOLD = 3;
public static final double VIP_DISCOUNT_RATE = 0.8;
if (userLevel > VIP_LEVEL_THRESHOLD) {
discount = price * VIP_DISCOUNT_RATE;
}
常量的三大核心意义:
- 消除魔术数字:
if (status == 2)不如if (status == ORDER_SHIPPED)清晰 - 集中修改:折扣率从 0.8 改为 0.75 只需修改一处常量定义
- 编译器优化:编译时常量会被内联,性能无损
飞翔科技常量使用示例
/**
* 飞翔科技 - 常量使用演示
* 场景:飞翔商城的业务常量定义
*/
public class FeixiangConstants {
// 编译时常量(static final)
public static final String COMPANY_NAME = "飞翔科技";
public static final int MAX_VIP_LEVEL = 5;
public static final double TAX_RATE = 0.06;
public static final int FREE_SHIPPING_THRESHOLD = 200; // 满200免运费
public static final String DEFAULT_CURRENCY = "CNY";
// 运行时常量(final,每个实例可不同)
private final String orderId; // 构造函数中赋值,之后不可变
public FeixiangConstants(String orderId) {
this.orderId = orderId; // 必须在构造函数中赋值
}
public static void main(String[] args) {
// 通过类名访问静态常量
System.out.println("欢迎来到" + COMPANY_NAME);
System.out.println("税率:" + (TAX_RATE * 100) + "%");
System.out.println("满" + FREE_SHIPPING_THRESHOLD + "元免运费");
// final 局部变量
final int buyCount = 3;
// buyCount = 5; // 编译错误!final变量不能重新赋值
System.out.println("购买数量:" + buyCount + "件(不可更改)");
}
}
运行输出:
欢迎来到飞翔科技
税率:6.0%
满200元免运费
购买数量:3件(不可更改)
面试考点
Q1:Java是强类型语言还是弱类型语言?
Java是强类型语言。每个变量在编译时就必须确定类型,且类型不可改变。这不同于JavaScript等弱类型语言,后者允许
var x = 10; x = "hello";。Java的强类型特性使得编译器能在编译期捕获大量类型错误,提高程序健壮性。
Q2:局部变量和成员变量(实例变量)的默认值有何不同?
成员变量(实例变量和静态变量)有JVM自动赋予的默认值:整数类型为0,浮点类型为0.0,boolean为false,char为
\u0000,引用类型为null。局部变量没有默认值,必须显式初始化后才能使用,否则编译报错。这是Java语言设计的安全机制:防止程序员读取栈上的随机数据。
Q3:final修饰的变量和普通变量有什么区别?
final修饰的变量只能赋值一次,赋值后不可更改。对于基本类型,值不可变;对于引用类型,引用(地址)不可变,但对象本身的内容可能可变。final常量通常使用全大写下划线命名(如MAX_SIZE),常用于定义配置常量、数学常量等。
Q4:变量名$price合法吗?_name合法吗?
$price合法,$是允许的字符(常用于编译器生成的代码)。_name在JDK 8中合法,但从JDK 9开始,单独的下划线_成为关键字,_name仍然合法。实际开发中不建议使用$或_开头,这通常是系统自动生成代码的命名惯例。