对 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 的按值传递和不变性感到困惑的主要内容,如果未能解决你的问题,请参考以下文章

Java的按值传递和按引用传递解说

String的按值传递,java传参都是传值

java的按值传递与按引用传递

String的按值传递,java传参都是传值(转)

选择 Java 函数调用中的按值传递行为

我是不是正确理解了 C 中的按值传递和按引用传递?