重新审视Jon Skeet在他的文章中发表的Java中的值语义传递

时间:2013-09-12 21:06:12

标签: java pass-by-reference pass-by-value stringbuffer

我通过reference semantics在Java Jon Skeet上阅读了这篇优秀的article,其中他说明了

  

我们假设存在一个名为f的过程,该过程采用形式参数s。我们称该函数为其提供实际参数g。

     

主叫代码:

         f( g )
     

功能:

   procedure f( s )
   begin
     -- body of the procedure
   end;
     

Java中的所有对象实例都在堆上分配,只能被访问       通过对象引用。如果我有以下内容:

       StringBuffer g = new StringBuffer( "Hello" );
     

变量g不包含字符串“Hello”,它包含对包含字符串“Hello”的对象实例的引用(或指针)。

     

因此,如果我再调用f(g),f可以自由修改其形式参数s       使其指向另一个StringBuffer或将其设置为null。       函数f还可以通过附加“World”来修改StringBuffer。虽然这会更改StringBuffer的值,但StringBuffer的值不是实际参数g的值。

我的理解可能是错的。下面的程序确实改变了传递给方法的Stringbuffer

public class MutabilityStringBuffer {
    public static void main(String[] args){
        StringBuffer sb = new StringBuffer("hello");
        System.out.println("String before append: "+ sb.toString());
        addString(sb);
        System.out.println("Sting after append "+ sb.toString());

        String s = "hello";
        System.out.println("String before append: "+ s);
        addString(s);
        System.out.println("Sting after append "+ s);

    }
    public static void addString(StringBuffer word){
        word.append(" world!");
    }
    public static void addString(String word){
        word+=" world!";
    }

}
当然,Jon Skeet也不错。但是我看到Stringbuffer可以通过传递给方法来改变,因为stringbuffer是可变的,这与Skeet发布的内容有点矛盾。请在这里明确我的理解。

由于

2 个答案:

答案 0 :(得分:2)

见评论

StringBuffer sb = new StringBuffer("hello"); // sb holds reference 
System.out.println("String before append: "+ sb.toString()); // you print the value 
addString(sb);  // you use the reference to append to the StringBuffer
System.out.println("Sting after append "+ sb.toString()); // you print the value

String s = "hello"; // s holds a refernece
System.out.println("String before append: "+ s); // you print its value
addString(s); // // the word variable would hold a new reference inside the method 
System.out.println("Sting after append "+ s); // you print its value

在这里

public static void addString(String word){
    word+=" world!";
}

当您使用

重新分配时,传递给word的引用的原始值会发生变化
word+=" world!"; 

它就是这样的

String word = [copy of value of the argument's reference];
word = word.toString() /* toString() is unnecessary, but just to make the point */ + " world";

其中String连接的结果是一个新的String对象,因此是一个新的引用。

以下

public static void addString(StringBuffer word){
    word.append(" world!");
}

您访问word引用的对象,在该对象上调用一个内部修改char[]的方法。所以你改变了对象的值,而不是引用的值。更改引用看起来像

public static void addString(StringBuffer word){
    word = new StringBuffer("Answer to the Ultimate Question of Life, the Universe, and Everything: ");
    word.append("42"); 
}

append对新的StringBuffer对象执行,而不是作为参数传递的对象,证明对象是按值传递的。

答案 1 :(得分:1)

这已被讨论过死亡。无论如何,考虑到一些空闲时间,这是我的回答。它与我总是给出的响应相同,它链接到我总是链接到的wikipedia article on evaluation strategies


我发现在描述Java中传递对象作为参数的语义行为(以及包括Python,C#等在内的许多其他语言)时,使用术语Call by (Object) Sharing可消除大多数歧义。这样做可以清楚地识别任何[im]可变性因素。也就是说,在方法中访问的对象与外部是同一个对象在此调用/评估策略期间,没有创建,复制,克隆或以其他方式复制新对象 - 所以任何 mutable 更改都会使对象影响相同的“共享”对象

使用Call by Value将对象的引用 - 传递给对象是一个实现细节,尽管在Java Language Specification (JLS)中有深入讨论。对于Java来说,更准确地说按引用的值调用对象 - 但这仍然是实现的 - 作为按值调用。 (但是,ECMAScript等某些语言在不讨论任何此类实现细节的情况下,实现完全相同的 Call by Sharing 语义。)

为了避免混淆,我使用Call by Reference仅表示被调用函数可以通过重新赋值修改调用者的变量绑定(与对象可变性正交) 参数。不幸的是,按参考呼叫在某些语言圈子中被赋予了另类 - 而且令人困惑的含义。