Skip to content
第 4 / 250 章后端⏱ 10 分钟阅读

第 4 章:变量与数据类型

学习目标

  • 理解什么是变量,为什么需要变量
  • 掌握 Java 的两大数据类型:基本类型 vs 引用类型
  • 理解内存分配视角下两者的本质区别
  • 学会正确命名变量
  • 了解变量作用域

一、为什么需要变量?

在第 3 章的 Hello World 里,"Hello, World!" 是一个字符串字面量,直接写在代码里。如果用户输入自己的名字呢?我们需要用变量来"记住"它:

java
public class Greeting {
    public static void main(String[] args) {
        String name = "张三";        // ① 把 "张三" 存到 name 变量里
        System.out.println("Hello, " + name + "!");
    }
}

变量就是给数据起一个名字,让程序能"记住"它、操作它。

类比生活:

二、变量的三个核心要素

任何变量都有三个核心要素:名字、类型、值

java
数据类型  变量名    =   初始值;
   ↑       ↑           ↑
  类型    标识符       字面量 / 表达式

示例

java
int age = 18;
 ↑   ↑    ↑
类型  名字  值

三要素详解

要素作用示例
数据类型决定变量能存什么数据,占多少内存intStringdouble
变量名标识符,访问这个数据的"钥匙"ageuserNameMAX_COUNT
初始值变量创建时的数据18"张三"3.14

三、Java 的两大数据类型

Java 是强类型语言,每个变量都必须显式声明类型。Java 把数据类型分为两大类:

类别关键字数量是否面向对象内存位置默认值
基本类型8 个栈(局部变量)/ 堆(成员变量)有各自默认值
引用类型无限多堆(对象本身)+ 栈(引用)null

第 5 章会详细讲 8 种基本类型,本章先建立全局认知。

四、从内存视角看变量

理解基本类型和引用类型的内存分配差异,是 Java 学习的第一个关键转折点。

基本类型的内存模型

基本类型的变量直接存储值本身,内存连续、紧凑。

引用类型的内存模型

引用类型的变量存储的是对象的内存地址(引用),实际对象数据在堆里。

这就是 Java 的"引用语义"

基本类型:变量持有值本身 引用类型:变量持有对象地址

这就是为什么 Java 的参数传递是"值传递"——传的是引用的副本,而不是对象本身(第 10 章会详解)。

五、声明变量的三种方式

方式 1:声明并立即初始化(推荐)

java
int age = 18;              // ① 声明 + 赋值
String name = "张三";       // ② 字符串类型
double salary = 12345.67;  // ③ 双精度浮点
boolean isActive = true;   // ④ 布尔

方式 2:先声明,后赋值

java
int age;        // ① 只声明,未初始化(成员变量有默认值,局部变量必须手动初始化)
age = 18;       // ② 赋值

方式 3:一次声明多个变量

java
int a = 1, b = 2, c = 3;     // ① 同行声明多个同类型变量

// 但不推荐这样写:可读性差
// ❌ 反例
int a=1,b=2,c=3;

为什么一行多个变量不推荐?

  1. 可读性差
  2. 调试时不易单步跟踪
  3. 后续重构(如批量修改类型)需要逐个改

阿里 Java 开发手册:单个方法体内不推荐声明过多变量。

六、变量命名规范

强制规则(违反会编译报错)

java
// ✅ 合法的标识符
String userName;
int _age;
double $price;
String 用户名;     // 中文也行,但不推荐

// ❌ 非法的标识符
// int 123abc;      // 不能以数字开头
// String user-name; // 不能有 -
// int class;        // 不能用关键字

合法标识符的规则:

推荐规则(行业最佳实践)

类型规则示例
局部变量 / 成员变量lowerCamelCaseuserNameorderList
常量UPPER_SNAKE_CASEMAX_RETRY_COUNTDEFAULT_TIMEOUT
类名UpperCamelCaseUserServiceOrderController
包名全小写,名词com.taskflow.user
布尔值变量is / has / can 前缀isActivehasPermission
集合变量用复数或类型后缀usersuserListuserSet

反面案例

java
// ❌ 反例 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;

不要用拼音!

拼音会让代码可读性暴跌,且任何中国公司都不会容忍拼音命名(阿里规约明文禁止)。

七、变量作用域

作用域 = 变量能被访问的代码范围。

三种作用域

示例代码

java
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)

字面量是直接写在代码里的固定值

常见字面量类型

java
// 整数字面量
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 表示"无引用"

常见坑

java
// ❌ 坑 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 对象引用

九、变量初始化与默认值

局部变量

必须显式初始化才能使用,否则编译报错:

java
public void method() {
    int x;          // ① 只声明,未初始化
    System.out.println(x);  // ❌ 编译报错:变量 x 可能尚未初始化

    int y = 0;      // ② 声明并初始化
    System.out.println(y);  // ✅ OK
}

类成员变量

有默认值,可以不显式初始化:

java
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):0
  • char'\u0000'(空字符)
  • booleanfalse
  • 引用类型:null

十、完整代码示例

java
/**
 * 变量与数据类型综合示例
 *
 * 配套文档:第 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: intInteger 的区别?

intInteger
类型基本类型引用类型(包装类)
默认值0null
内存栈,4 字节堆(对象),约 16 字节
是否面向对象是(可以放进 List 等泛型集合)
比较== 比较值equals 比较值,== 比较地址

详细在 第 6 章:自动装箱与拆箱 讲解。

Q2: 为什么 Java 要区分基本类型和引用类型?

性能:基本类型直接存值,访问速度快;引用类型需要先通过地址找对象,多一次寻址。

历史原因:Java 诞生时(1995)面向对象是主流,但为了数值计算的性能保留了基本类型。

现代语言(如 Kotlin、Scala)已经全部统一为引用类型(用对象包装基本类型)。

Q3: 局部变量和成员变量重名怎么办?

java
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,声明并打印以下变量:

java
String username = "你的名字";
int age = 你的年龄;
double height = 你的身高(米);
boolean isStudent = true;
char grade = 'A';

输出格式

=== 个人档案 ===
姓名:张三
年龄:18
身高:1.75 米
是否学生:是
等级:A

练习 2:进阶题

写一个 TypeConversion.java,演示各种类型转换:

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 变量的值(不用第三个变量)。

提示:用加减法或异或运算。

java
int a = 5, b = 10;
// 你的代码:交换 a 和 b
System.out.println("a = " + a + ", b = " + b);  // 期望 a = 10, b = 5

提示

java
// 方法 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;

推荐阅读


下一章第 5 章:八种基本数据类型

你已经完成了

  • ✅ 理解变量的本质和三要素
  • ✅ 区分基本类型 vs 引用类型
  • ✅ 从内存视角理解两种类型的差异
  • ✅ 掌握变量命名规范和作用域
  • ✅ 了解类型转换和字面量

本站基于 VitePress 构建 · 由 Codebook 团队维护