对 Java 的按值传递和不变性感到困惑
Posted
技术标签:
【中文标题】对 Java 的按值传递和不变性感到困惑【英文标题】:Confusion over Java's pass-by-value and immutability 【发布时间】:2012-01-11 12:11:39 【问题描述】:在准备 SCJP(或现在已知的 OCPJP)考试时,我遇到了一些关于传递(引用)值和不变性的模拟问题。
我的理解是,当您将一个变量传递给一个方法时,您传递的是代表如何获取该变量的位的副本,而不是实际的对象本身。
您发送的副本指向同一个对象,因此如果该对象是可变的,您可以修改该对象,例如附加到 StringBuilder。但是,如果您对不可变对象执行某些操作,例如递增 Integer,则局部引用变量现在指向一个新对象,而原始引用变量仍然不会注意到这一点。
在这里考虑我的例子:
public class PassByValueExperiment
public static void main(String[] args)
StringBuilder sb = new StringBuilder();
sb.append("hello");
doSomething(sb);
System.out.println(sb);
Integer i = 0;
System.out.println("i before method call : " + i);
doSomethingAgain(i);
System.out.println("i after method call: " + i);
private static void doSomethingAgain(Integer localI)
// Integer is immutable, so by incrementing it, localI refers to newly created object, not the existing one
localI++;
private static void doSomething(StringBuilder localSb)
// localSb is a different reference variable, but points to the same object on heap
localSb.append(" world");
问题:是不是只有不可变对象才有这种行为,而可变对象可以通过传值引用来修改?我的理解是否正确,或者这种行为还有其他好处吗?
【问题讨论】:
private static void doSomething(StringBuilder localSb) localSb = new StringBuilder("现在发生了什么"); 【参考方案1】:在语言级别上,可变对象和不可变对象之间没有区别 - 不可变性纯粹是类 API 的属性。
这个事实只是被自动装箱弄糊涂了,它允许++
用于包装器类型,使其看起来像对对象的操作 - 但事实并非如此,正如您自己注意到的那样。相反,它是一种语法糖,用于将值转换为基元、递增、将结果转换回包装器类型并将对该变量的引用分配给变量。
因此,区别在于++
运算符在原语和包装器上的作用,后者与参数传递没有任何关系。
【讨论】:
+1 如果您认为localI = new Integer(localI.intValue()+1);
是正在执行的实际代码,那么一切都会更加清晰。【参考方案2】:
Java 本身不知道对象是否不可变。在每种情况下,您都传递参数的 value,它可以是引用或原始值。更改参数的值永远不会有任何效果。
现在,澄清一下,这段代码不会改变参数的值:
localSb.append(" world");
这改变了参数值所引用的对象内的数据,这是非常不同的。请注意,您没有为 localSb
分配新值。
从根本上说,您需要了解:
表达式(变量、参数、参数等)的值总是是引用或原始值。它从不是一个对象。 Java 总是 使用按值传递的语义。参数的值成为参数的初始值。一旦你仔细思考了这些事情,并在你的脑海中区分了“变量”、“值”和“对象”的概念,事情就会变得更加清晰。
【讨论】:
以上是关于对 Java 的按值传递和不变性感到困惑的主要内容,如果未能解决你的问题,请参考以下文章