第 4 章:变量与数据类型
学习目标
- 理解什么是变量,为什么需要变量
- 掌握 Java 的两大数据类型:基本类型 vs 引用类型
- 理解内存分配视角下两者的本质区别
- 学会正确命名变量
- 了解变量作用域
一、为什么需要变量?
在第 3 章的 Hello World 里,"Hello, World!" 是一个字符串字面量,直接写在代码里。如果用户输入自己的名字呢?我们需要用变量来"记住"它:
public class Greeting {
public static void main(String[] args) {
String name = "张三"; // ① 把 "张三" 存到 name 变量里
System.out.println("Hello, " + name + "!");
}
}变量就是给数据起一个名字,让程序能"记住"它、操作它。
类比生活:
二、变量的三个核心要素
任何变量都有三个核心要素:名字、类型、值。
数据类型 变量名 = 初始值;
↑ ↑ ↑
类型 标识符 字面量 / 表达式示例
int age = 18;
↑ ↑ ↑
类型 名字 值三要素详解
| 要素 | 作用 | 示例 |
|---|---|---|
| 数据类型 | 决定变量能存什么数据,占多少内存 | int、String、double |
| 变量名 | 标识符,访问这个数据的"钥匙" | age、userName、MAX_COUNT |
| 初始值 | 变量创建时的数据 | 18、"张三"、3.14 |
三、Java 的两大数据类型
Java 是强类型语言,每个变量都必须显式声明类型。Java 把数据类型分为两大类:
| 类别 | 关键字数量 | 是否面向对象 | 内存位置 | 默认值 |
|---|---|---|---|---|
| 基本类型 | 8 个 | 否 | 栈(局部变量)/ 堆(成员变量) | 有各自默认值 |
| 引用类型 | 无限多 | 是 | 堆(对象本身)+ 栈(引用) | null |
第 5 章会详细讲 8 种基本类型,本章先建立全局认知。
四、从内存视角看变量
理解基本类型和引用类型的内存分配差异,是 Java 学习的第一个关键转折点。
基本类型的内存模型
基本类型的变量直接存储值本身,内存连续、紧凑。
引用类型的内存模型
引用类型的变量存储的是对象的内存地址(引用),实际对象数据在堆里。
这就是 Java 的"引用语义"
基本类型:变量持有值本身 引用类型:变量持有对象地址
这就是为什么 Java 的参数传递是"值传递"——传的是引用的副本,而不是对象本身(第 10 章会详解)。
五、声明变量的三种方式
方式 1:声明并立即初始化(推荐)
int age = 18; // ① 声明 + 赋值
String name = "张三"; // ② 字符串类型
double salary = 12345.67; // ③ 双精度浮点
boolean isActive = true; // ④ 布尔方式 2:先声明,后赋值
int age; // ① 只声明,未初始化(成员变量有默认值,局部变量必须手动初始化)
age = 18; // ② 赋值方式 3:一次声明多个变量
int a = 1, b = 2, c = 3; // ① 同行声明多个同类型变量
// 但不推荐这样写:可读性差
// ❌ 反例
int a=1,b=2,c=3;为什么一行多个变量不推荐?
- 可读性差
- 调试时不易单步跟踪
- 后续重构(如批量修改类型)需要逐个改
阿里 Java 开发手册:单个方法体内不推荐声明过多变量。
六、变量命名规范
强制规则(违反会编译报错)
// ✅ 合法的标识符
String userName;
int _age;
double $price;
String 用户名; // 中文也行,但不推荐
// ❌ 非法的标识符
// int 123abc; // 不能以数字开头
// String user-name; // 不能有 -
// int class; // 不能用关键字合法标识符的规则:
推荐规则(行业最佳实践)
| 类型 | 规则 | 示例 |
|---|---|---|
| 局部变量 / 成员变量 | lowerCamelCase | userName、orderList |
| 常量 | UPPER_SNAKE_CASE | MAX_RETRY_COUNT、DEFAULT_TIMEOUT |
| 类名 | UpperCamelCase | UserService、OrderController |
| 包名 | 全小写,名词 | com.taskflow.user |
| 布尔值变量 | 用 is / has / can 前缀 | isActive、hasPermission |
| 集合变量 | 用复数或类型后缀 | users、userList、userSet |
反面案例
// ❌ 反例 1:用拼音
String yhxm = "张三"; // 应为 userName
int nj = 18; // 应为 age
// ❌ 反例 2:缩写不一致
String getUsrName(); // 一会儿 User 一会儿 Usr
String getUserID(); // 应为 getUserId
// ❌ 反例 3:意义不明
int a = 1;
int b = 2;
int c = a + b; // 看不出在算什么
// ✅ 正例 1:英文命名
String userName = "张三";
int age = 18;
// ✅ 正例 2:意义明确
int price = 100;
int quantity = 2;
int total = price * quantity;不要用拼音!
拼音会让代码可读性暴跌,且任何中国公司都不会容忍拼音命名(阿里规约明文禁止)。
七、变量作用域
作用域 = 变量能被访问的代码范围。
三种作用域
示例代码
public class ScopeDemo {
// ① 类成员变量(Field):整个类内可见
private int memberVar = 100;
public void method(int param) { // ② 方法参数:整个 method 方法内可见
int localVar = 200; // ③ 局部变量:从声明处到方法结束可见
if (true) {
int blockVar = 300; // ④ 块变量:只在 if 块内可见
System.out.println(blockVar);
System.out.println(localVar);
System.out.println(param);
System.out.println(memberVar);
}
// System.out.println(blockVar); // ❌ 编译报错:块外无法访问
}
}作用域图解
作用域规则:内层作用域可以访问外层变量,反之不行。
八、字面量(Literal)
字面量是直接写在代码里的固定值。
常见字面量类型
// 整数字面量
int decimal = 100; // ① 十进制
int hex = 0xFF; // ② 十六进制(255)
int octal = 077; // ③ 八进制(63)
int binary = 0b1010; // ④ 二进制(10,JDK 7+)
long bigNum = 100L; // ⑤ 长整型加 L 后缀
// 浮点字面量
double pi = 3.14; // ⑥ 默认 double
float price = 3.14f; // ⑦ float 加 f 后缀
double scientific = 1.5e3; // ⑧ 科学计数法(1500.0)
// 字符字面量
char a = 'A'; // ⑨ 单个字符用单引号
char newline = '\n'; // ⑩ 转义字符
char unicode = '\u4e2d'; // ⑪ Unicode 字符("中")
// 字符串字面量
String greeting = "Hello"; // ⑫ 字符串用双引号
// 布尔字面量
boolean flag = true;
boolean done = false;
// null 字面量
String str = null; // ⑬ null 表示"无引用"常见坑
// ❌ 坑 1:长整型不加 L,默认是 int
long creditCardNumber = 1234567890123456; // 编译报错(超过 int 范围)
long creditCardNumber = 1234567890123456L; // ✅ 加 L 后缀
// ❌ 坑 2:浮点默认 double,赋值给 float 要加 f
float pi = 3.14; // 编译报错(double 转 float 精度损失)
float pi = 3.14f; // ✅ 加 f 后缀
// ❌ 坑 3:字符用双引号会被识别为字符串
char c = "A"; // 编译报错(类型不匹配)
char c = 'A'; // ✅ 用单引号
// ❌ 坑 4:String 不是基本类型
String s = "hello"; // 看起来简单,实际是引用类型,存储的是 String 对象引用九、变量初始化与默认值
局部变量
必须显式初始化才能使用,否则编译报错:
public void method() {
int x; // ① 只声明,未初始化
System.out.println(x); // ❌ 编译报错:变量 x 可能尚未初始化
int y = 0; // ② 声明并初始化
System.out.println(y); // ✅ OK
}类成员变量
有默认值,可以不显式初始化:
public class Defaults {
private byte b; // 默认 0
private short s; // 默认 0
private int i; // 默认 0
private long l; // 默认 0
private float f; // 默认 0.0
private double d; // 默认 0.0
private char c; // 默认 '\u0000'(空字符)
private boolean flag; // 默认 false
private String str; // 默认 null(引用类型)
private int[] arr; // 默认 null
}默认值速记:
- 数值类型(byte/short/int/long/float/double):
0char:'\u0000'(空字符)boolean:false- 引用类型:
null
十、完整代码示例
/**
* 变量与数据类型综合示例
*
* 配套文档:第 4 章 变量与数据类型
* 运行方式:javac VariableDemo.java && java VariableDemo
*/
public class VariableDemo {
// ============ 成员变量(类级别作用域) ============
private static int instanceCount = 0; // ① static 成员变量(属于类,所有对象共享)
private String instanceName; // ② 实例变量(每个对象独立)
public static void main(String[] args) {
// ============ 局部变量(方法级别作用域) ============
int localAge = 18; // ③ 基本类型局部变量
String localName = "张三"; // ④ 引用类型局部变量
System.out.println("=== 局部变量 ===");
System.out.println("name = " + localName);
System.out.println("age = " + localAge);
// ============ 变量重新赋值 ============
localAge = 19; // ⑤ 变量可以被重新赋值
System.out.println("修改后的 age = " + localAge);
// ============ 引用类型的特性 ============
String greeting = "Hello";
String sameGreeting = greeting; // ⑥ sameGreeting 和 greeting 指向同一个字符串对象
System.out.println("greeting == sameGreeting: " + (greeting == sameGreeting)); // true
System.out.println("greeting.equals(sameGreeting): " + greeting.equals(sameGreeting)); // true
// ============ 块作用域 ============
if (localAge >= 18) {
String status = "成年"; // ⑦ 块作用域变量
System.out.println("状态: " + status);
}
// System.out.println(status); // ❌ 编译报错:status 在 if 块外不可见
// ============ 类型转换示例 ============
int intValue = 100;
double doubleValue = intValue; // ⑧ 自动类型转换(int → double)
System.out.println("int 转 double: " + doubleValue);
// double backToInt = 3.14;
// int narrowed = backToInt; // ❌ 编译报错:double 转 int 需要强转
int narrowed = (int) 3.14; // ⑨ 强制类型转换(精度损失:3.14 → 3)
System.out.println("double 强转 int: " + narrowed);
// ============ final 修饰的常量 ============
final double PI = 3.14159; // ⑩ final 变量 = 常量,不可修改
System.out.println("PI = " + PI);
// PI = 3.14; // ❌ 编译报错:final 变量不能重新赋值
}
}运行结果:
=== 局部变量 ===
name = 张三
age = 18
修改后的 age = 19
greeting == sameGreeting: true
greeting.equals(sameGreeting): true
状态: 成年
int 转 double: 100.0
double 强转 int: 3
PI = 3.14159十一、常见面试题
Q1: int 和 Integer 的区别?
int | Integer | |
|---|---|---|
| 类型 | 基本类型 | 引用类型(包装类) |
| 默认值 | 0 | null |
| 内存 | 栈,4 字节 | 堆(对象),约 16 字节 |
| 是否面向对象 | 否 | 是(可以放进 List 等泛型集合) |
| 比较 | == 比较值 | equals 比较值,== 比较地址 |
详细在 第 6 章:自动装箱与拆箱 讲解。
Q2: 为什么 Java 要区分基本类型和引用类型?
性能:基本类型直接存值,访问速度快;引用类型需要先通过地址找对象,多一次寻址。
历史原因:Java 诞生时(1995)面向对象是主流,但为了数值计算的性能保留了基本类型。
现代语言(如 Kotlin、Scala)已经全部统一为引用类型(用对象包装基本类型)。
Q3: 局部变量和成员变量重名怎么办?
public class Shadow {
private int x = 10; // 成员变量
public void method() {
int x = 20; // 局部变量,**遮蔽**了成员变量
System.out.println(x); // 20(局部变量)
System.out.println(this.x); // 10(通过 this 访问成员变量)
}
}这种"遮蔽"会让代码可读性变差,应避免。
十二、本章小结
| 要点 | 关键 |
|---|---|
| 变量的本质 | 给数据起名,让程序能"记住"和操作它 |
| 两大类型 | 基本类型(8 种,直接存值)vs 引用类型(无限种,存地址) |
| 内存分配 | 基本类型 → 栈,引用类型 → 栈存地址 + 堆存对象 |
| 命名规范 | lowerCamelCase(变量)/ UPPER_SNAKE_CASE(常量) |
| 作用域 | 成员变量(类内)/ 局部变量(方法内)/ 块变量(块内) |
| 初始化 | 局部变量必须初始化,成员变量有默认值 |
| 字面量 | 直接写在代码里的固定值(100, "hello", true, null 等) |
动手练习
练习 1:基础题
写一个 UserProfile.java,声明并打印以下变量:
String username = "你的名字";
int age = 你的年龄;
double height = 你的身高(米);
boolean isStudent = true;
char grade = 'A';输出格式:
=== 个人档案 ===
姓名:张三
年龄:18
身高:1.75 米
是否学生:是
等级:A练习 2:进阶题
写一个 TypeConversion.java,演示各种类型转换:
int i = 100;
long l = i; // 自动转换 int → long
float f = l; // 自动转换 long → float
double d = f; // 自动转换 float → double
System.out.println("i = " + i);
System.out.println("l = " + l);
System.out.println("f = " + f);
System.out.println("d = " + d);
// 反向转换(需要强转)
double pi = 3.14159;
int truncated = (int) pi; // 强制转换
System.out.println("pi 截断 = " + truncated);练习 3:挑战题
写一个 SwapDemo.java,交换两个 int 变量的值(不用第三个变量)。
提示:用加减法或异或运算。
int a = 5, b = 10;
// 你的代码:交换 a 和 b
System.out.println("a = " + a + ", b = " + b); // 期望 a = 10, b = 5提示
// 方法 1:加减法(有溢出风险)
a = a + b; // a = 15
b = a - b; // b = 5
a = a - b; // a = 10
// 方法 2:异或(无溢出风险)
a = a ^ b;
b = a ^ b;
a = a ^ b;推荐阅读
- 📖 《Java 核心技术》卷 1 第 3 章 — 数据类型详解
- 🌐 Java 官方教程:变量 — Oracle 权威
- 📖 《码出高效:Java 开发手册》 — 阿里规约背后的设计哲学
下一章:第 5 章:八种基本数据类型 →
你已经完成了:
- ✅ 理解变量的本质和三要素
- ✅ 区分基本类型 vs 引用类型
- ✅ 从内存视角理解两种类型的差异
- ✅ 掌握变量命名规范和作用域
- ✅ 了解类型转换和字面量