一、Java 只有值传递
Java 中方法参数的传递方式只有值传递(Pass by Value),但根据参数类型不同,传递的内容有所区别:
- 基本数据类型(int、double 等):传递的是数值的副本。
- 引用数据类型(String、List、对象等):传递的是引用的副本。
引用可以简单理解为指向对象的地址(类似 C/C++ 的指针,但不一定是真实的内存地址)。
例如:
List<String> list = new ArrayList<>();
change(list);
假设 ArrayList 对象位于 0x100,则参数传递过程可以理解为:
main 方法:
list = 0x100 ─────┐
├──→ ArrayList 对象
change 方法: │
x = 0x100 ─────┘
list 和 x 是两个独立的引用变量,只不过它们保存了相同的引用值,指向同一个对象。
二、为什么 List 在方法中修改后,外面也会改变?
public static void main(String[] args) {
List<String> list = new ArrayList<>();
list.add("A");
change(list);
System.out.println(list); // [A, B]
}
public static void change(List<String> x) {
x.add("B");
}
原因是 x.add("B") 修改的是引用指向的 ArrayList 对象,而不是引用变量本身。
因此,list 和 x 指向的同一个对象发生了变化,外面的 list 自然能观察到。
但是,如果重新给参数赋值:
public static void change(List<String> x) {
x = new ArrayList<>();
x.add("B");
}
外面的 list 不会改变。
因为 x = new ArrayList<>() 只是让局部变量 x 指向一个新对象,原来的 list 仍然指向旧对象。
三、为什么 String 在方法中修改后,外面不会改变?
String 同样是引用类型,但它是不可变对象(Immutable Object)。
例如:
public static void main(String[] args) {
String str = "ABC";
change(str);
System.out.println(str); // ABC
}
public static void change(String s) {
s = "DEF";
}
假设 "ABC" 对象的地址为 0x100,"DEF" 对象的地址为 0x200。
参数刚传入时:
str = 0x100 ────→ "ABC"
s = 0x100 ────→ "ABC"
执行 s = "DEF" 后:
str = 0x100 ────→ "ABC"
s = 0x200 ────→ "DEF"
这里并没有修改原来的 "ABC" 对象,而是修改了局部引用变量 s 的指向。
而且 String 不可变,concat()、replace() 等方法不会修改原来的字符串对象,而是返回结果字符串。
public static void change(String s) {
s = s.concat("DEF");
}
同样不会影响外面的 str。
四、String 和 StringBuilder 的区别
public static void main(String[] args) {
String str = "ABC";
StringBuilder sb = new StringBuilder("ABC");
change1(str);
change2(sb);
System.out.println(str); // ABC
System.out.println(sb); // ABCDEF
}
public static void change1(String s) {
s = s + "DEF";
}
public static void change2(StringBuilder s) {
s.append("DEF");
}
原因:
String不可变,s + "DEF"得到新的字符串,再让局部变量s指向它。StringBuilder可变,append()直接修改原对象,因此外部可以观察到变化。
五、总结
| 操作 | 是否影响外面的对象或变量 | 原因 |
|---|---|---|
int x = 20 |
否 | 修改基本类型参数的副本 |
list.add("A") |
是 | 修改共同指向的对象 |
list.clear() |
是 | 修改共同指向的对象 |
list = new ArrayList<>() |
否 | 只改变局部引用的指向 |
str = "DEF" |
否 | 只改变局部引用的指向 |
str = str.concat("DEF") |
否 | 结果重新赋给局部引用 |
sb.append("DEF") |
是 | 修改原 StringBuilder 对象 |
核心结论:
Java 只有值传递。引用类型传递的是引用的副本,而不是对象的副本。
需要区分两种操作:
- 修改引用指向的对象: 如果对象可变,外部可以观察到对象内容的变化,例如
list.add()。 - 修改引用变量的指向: 不会影响外部引用,例如
list = new ArrayList<>()、str = "DEF"。
String 与 List 的传参机制完全相同,区别仅在于 String 是不可变对象(其实就是说你不能改变String对象的某部分,你修改、赋值等返回的都是一个新对象),而 List 通常是可变对象(就是你可以修改、删除List中的某个值啊,这个List对象还是原来的对象)。
浙公网安备 33010602011771号