引用类型的可变性关键在于“改的是对象状态还是引用本身”:共享引用时修改对象属性会影响所有引用,而重赋值仅改变当前变量指向;不可变类型每次“修改”实为创建新对象。

引用类型的可变性,关键不在“能不能改”,而在于“改的是什么”——是对象内部的状态,还是变量所持有的那个地址(即引用本身)。
对象属性修改:共享同一块内存时,改一个,全看到
当两个变量引用同一个对象(比如 Java 中的 Person p1 = new Person("A"); Person p2 = p1;),它们指向堆中同一块内存。此时通过任一引用修改对象字段(如 p2.setName("B")),p1.getName() 也会返回 "B"。这不是“复制了数据”,而是操作了同一份数据。
类似情况也出现在 Python 列表 或 C# StringBuilder 中:调用 .append() 或 .Append() 不会换地址,只是在原对象上增删内容。
引用指向改变:只影响当前变量,不波及其他引用
如果把一个引用变量重新赋值为另一个对象(如 p1 = new Person("C");),这只是让 p1 指向新地址,p2 仍牢牢指向原来的对象。原始对象不会消失,其他引用也不会被通知或同步更新。
这个行为在所有主流语言中一致:
- Java 方法内 p = new Person(...) 不会影响外部传入的引用;
- Python 中 a = [1,2]; b = a; a = [3,4] 后,b 还是 [1,2];
- C# 里 sb1 = new StringBuilder("X") 只改变 sb1 的指向,不影响之前和它共用对象的其他变量。
不可变类型“假装可变”:每次操作都生成新对象
像 string、tuple、Integer(Java 包装类)这类不可变引用类型,表面看能“修改”,实则是创建新实例:
-
str1 = "hello"; str1 += " world"→str1现在指向新字符串对象,原字符串未动; -
ImmutablePerson p1 = new ImmutablePerson("Tom", 25); var p2 = p1.WithAge(26)→p1仍是原对象,p2是全新实例; - Java 中
String s = "a"; s = s + "b",两次s指向不同对象,哈希码也不同。
判断可变性的实际依据:有没有“就地修改”的能力
不要只看类型名,要看它是否提供能直接变更内部状态的方法:
- 有
.Add()、.Clear()、.setXXX()、.append()等方法 → 通常是可变的; - 只有只读属性(
get)、构造函数初始化、返回新实例的“变换方法”(如WithXXX())→ 倾向于不可变; - 即使类型声明为
readonly或final,若内部字段可变(如final List<T>但允许add()),整体仍是可变的。


















