Java中String、ArrayList、HashMap等引用类型变量存储的是堆内存地址,=赋值复制地址而非内容;修改可变对象会影响所有引用,String因不可变而例外;==比较地址,equals比较内容;传参时地址副本仍指向原对象,需注意副作用与null风险。

String、ArrayList、HashMap 这些不是“变量值”,而是指向堆内存中对象的引用。直接用 = 赋值,复制的是地址,不是内容。
引用赋值本质是地址拷贝
Java 中所有非基本类型的变量(如 String、MyClass、int[])存储的都是对象在堆中的内存地址。当你写:
String a = "hello"; String b = a;
b 得到的不是新字符串,而是和 a 指向同一块堆内存。修改可变对象(如 ArrayList)的内容会影响所有引用;但对 String 重新赋值(a = "world")不会影响 b,因为 String 不可变,赋值实际是让 a 指向新地址。
- 基本类型(
int、boolean等)赋值:拷贝值本身 - 引用类型赋值:拷贝地址(4 或 8 字节的指针)
- 判断是否同一对象:用
==比较地址,用.equals()比较逻辑内容
常见误判:以为 == 能比较字符串内容
这是新手最常踩的坑。以下代码输出 false:
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); // false
原因:new String() 强制在堆上创建两个不同对象,== 比的是地址。正确做法是:
立即学习“Java免费学习笔记(深入)”;
- 用
s1.equals(s2)判断内容是否相等 - 若确定字符串来自字面量或已 intern,可用
s1 == s2(但不推荐依赖) - 注意
Objects.equals(s1, s2)可防null,更安全
数组和集合传参时修改会反映到原对象
方法参数是引用类型时,传入的是地址副本——仍指向原对象。因此:
void append(Listlist) { list.add("new item"); // 原 list 会被修改 } List data = new ArrayList<>(); append(data); System.out.println(data.size()); // 输出 1
- 想避免副作用?方法内应创建副本:
new ArrayList(list) - 数组同理:
int[] arr传参后,arr[0] = 99会改原始数组 - 但若在方法里重赋值:
list = new ArrayList();不影响外部变量
容易被忽略的 null 引用风险
引用变量初始值为 null,未初始化就调用方法会抛 NullPointerException:
-
String s;声明后未赋值,s.length()直接崩溃 - 集合类(如
Map)的get()方法可能返回null,需提前检查 - 现代写法倾向用
Optional显式表达“可能为空”,但不要滥用 - 构造函数/方法入口处用
Objects.requireNonNull(param, "param must not be null")快速失败
引用类型的核心不在“怎么声明”,而在“谁持有地址、何时共享、是否可变”。搞清这三点,多数空指针、意外交互、深浅拷贝问题自然浮现。









