对Java的传值和不变性的困惑

时间:2011-12-02 13:08:01

标签: java immutability pass-by-value scjp ocpjp

为了准备SCJP(或现在已知的OCPJP)考试,我被一些关于传递(参考)价值和不变性的模拟问题所困扰。

我的理解是,当你将一个变量传递给一个方法时,你会传递一个代表如何获取该变量的位的副本,而不是实际的对象本身。

您发送的副本指向同一个对象,因此您可以修改该对象(如果它是可变的),例如附加到StringBuilder。但是,如果对不可变对象执行某些操作(例如递增整数),则本地引用变量现在指向新对象,并且原始引用变量仍然无视此。

考虑我的例子:

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");
    }
}

问题:是否只有不可变对象以这种方式运行,并且可以通过值传递引用来修改可变对象?我的理解是正确的还是在这种行为中有其他好处?

2 个答案:

答案 0 :(得分:6)

语言级别的可变对象和可变对象之间没有区别 - 不变性纯粹是类API的属性。

这个事实只是被自动装箱弄糊涂了,这允许++在包装类型上使用,使它看起来像是对象的操作 - 但它并不是真的,因为你已经注意到了自己。相反,它是用于将值转换为基元的语法糖,将其递增,将结果转换回包装类型并将该引用分配给变量。

因此,++运算符在基元和包装器上使用时的区别实际上是区别的,这与参数传递没有任何关系。

答案 1 :(得分:5)

Java本身不知道对象是否是不可变的。在每种情况下,都传递参数的,它是引用值或原始值。更改参数的值永远不会产生任何影响。

现在,澄清一下,此代码不会更改参数的值:

localSb.append(" world");

这会更改参数值引用的对象内的数据,这是非常不同的。请注意,您没有为localSb分配新值。

从根本上说,你需要理解:

  • 表达式(变量,参数,参数等)的值始终是引用值或原始值。它从不一个对象。
  • Java 始终使用按值传递语义。参数的值成为参数的初始值。

一旦仔细思考这些事情,并在脑海中分离出“变量”,“价值”和“对象”的概念,事情就会变得更加清晰。