在Java中通过引用传递字符串?

在Java中通过引用传递字符串?,java,string,pass-by-reference,Java,String,Pass By Reference,我习惯于在C中执行以下操作: void main() { String zText = ""; fillString(zText); printf(zText); } void fillString(String zText) { zText += "foo"; } 输出为: foo 然而,在Java中,这似乎不起作用。我假设是因为字符串对象是复制的,而不是通过引用传递的。我认为字符串是对象,总是通过引用传递 这里发生了什么?java.lang.String

我习惯于在
C
中执行以下操作:

void main() {
    String zText = "";
    fillString(zText);
    printf(zText);
}

void fillString(String zText) {
    zText += "foo";
}
输出为:

foo
然而,在Java中,这似乎不起作用。我假设是因为
字符串
对象是复制的,而不是通过引用传递的。我认为字符串是对象,总是通过引用传递


这里发生了什么?

java.lang.String是不可变的


我讨厌粘贴URL,但如果你在java世界,这对你阅读和理解是必不可少的。

字符串是java语言

您有三种选择:

  • 使用StringBuilder:

    StringBuilder zText = new StringBuilder ();
    void fillString(StringBuilder zText) { zText.append ("foo"); }
    
  • 创建容器类并将容器实例传递给方法:

    public class Container { public String data; }
    void fillString(Container c) { c.data += "foo"; }
    
  • 创建一个数组:

    new String[] zText = new String[1];
    zText[0] = "";
    
    void fillString(String[] zText) { zText[0] += "foo"; }
    

  • 从性能的角度来看,StringBuilder通常是最好的选择。

    在Java中,没有任何东西是通过引用传递的。一切都是通过值传递的。对象引用是按值传递的。此外,字符串是不可变的。因此,当您附加到传递的字符串时,您只会得到一个新字符串。您可以使用返回值,也可以传递StringBuffer。

    发生的情况是,引用按值传递,即传递引用的副本。java中没有任何内容是通过引用传递的,而且由于字符串是不可变的,因此该赋值创建了一个新的字符串对象,引用的副本现在指向该对象。原始引用仍然指向空字符串

    class StringFiller {
      static void fillString(String zText) {
        zText += "foo";
        System.out.println("Local value: " + zText);
      }
    
      public static void main(String[] args) {
        String zText = "";
        System.out.println("Original value: " + zText);
        fillString(zText);
        System.out.println("Final value: " + zText);
      }
    }
    
    这对于任何对象都是一样的,即在方法中将其设置为新值。下面的例子只是让事情变得更加明显,但是连接字符串实际上是一样的

    void foo( object o )
    {
        o = new Object( );  // original reference still points to old value on the heap
    }
    

    Java中的所有参数都是按值传递的。将
    字符串
    传递给函数时,传递的值是对字符串对象的引用,但不能修改该引用,并且基础字符串对象是不可变的

    任务

    zText += foo;
    
    相当于:

    zText = new String(zText + "foo");
    
    也就是说,它(本地)将参数
    zText
    重新指定为一个新的引用,该引用指向一个新的内存位置,其中是一个新的
    字符串
    ,该字符串包含
    zText
    的原始内容,并附加了
    “foo”

    原始对象未被修改,
    main()
    方法的局部变量
    zText
    仍然指向原始(空)字符串

    印刷品:

    Original value:
    Local value: foo
    Final value:
    
    Original value:
    Local value: foo
    Final value: foo
    
    如果要修改字符串,可以使用
    StringBuilder
    或其他容器(数组或
    AtomicReference
    或自定义容器类),为您提供额外级别的指针间接寻址。或者,只需返回新值并将其赋值:

    class StringFiller2 {
      static String fillString(String zText) {
        zText += "foo";
        System.out.println("Local value: " + zText);
        return zText;
      }
    
      public static void main(String[] args) {
        String zText = "";
        System.out.println("Original value: " + zText);
        zText = fillString(zText);
        System.out.println("Final value: " + zText);
      }
    }
    
    印刷品:

    Original value:
    Local value: foo
    Final value:
    
    Original value:
    Local value: foo
    Final value: foo
    
    在一般情况下,这可能是最类似Java的解决方案——请参阅有效的Java项目“Favor immutability”

    不过,如前所述,
    StringBuilder
    通常会给您带来更好的性能——如果您有很多附加工作要做,特别是在循环中,请使用
    StringBuilder


    但是如果可以的话,尝试传递不可变的
    字符串
    而不是可变的
    StringBuilders
    ——您的代码将更易于阅读和维护。考虑将参数“代码>最终< /COD>”,并在将参数重新分配给新值时配置IDE警告您。

    字符串是java中的一个特殊类。它是线程安全的,这意味着“一旦创建了字符串实例,字符串实例的内容将永远不会更改”

    这就是发生的事情

     zText += "foo";
    
    首先,Java编译器将获取zText字符串实例的值,然后创建一个新的字符串实例,该实例的值为zText并附加“foo”。所以您知道为什么zText指向的实例没有更改。这是一个全新的例子。事实上,甚至字符串“foo”也是一个新的字符串实例。因此,对于该语句,Java将创建两个字符串实例,一个是“foo”,另一个是zText append“foo”的值。 规则很简单:字符串实例的值永远不会更改

    对于方法fillString,您可以使用StringBuffer作为参数,也可以如下更改:

    String fillString(String zText) {
        return zText += "foo";
    }
    

    对象通过引用传递,基本体通过值传递

    字符串不是一个原语,它是一个对象,它是对象的一个特例

    这是为了节省内存。在JVM中,有一个字符串池。对于创建的每个字符串,JVM将尝试查看字符串池中是否存在相同的字符串,如果已经存在,则指向它

    public class TestString
    {
        private static String a = "hello world";
        private static String b = "hello world";
        private static String c = "hello " + "world";
        private static String d = new String("hello world");
    
        private static Object o1 = new Object();
        private static Object o2 = new Object();
    
        public static void main(String[] args)
        {
            System.out.println("a==b:"+(a == b));
            System.out.println("a==c:"+(a == c));
            System.out.println("a==d:"+(a == d));
            System.out.println("a.equals(d):"+(a.equals(d)));
            System.out.println("o1==o2:"+(o1 == o2));
    
            passString(a);
            passString(d);
        }
    
        public static void passString(String s)
        {
            System.out.println("passString:"+(a == s));
        }
    }
    
    /*输出*/

    a==b:true
    a==c:true
    a==d:false
    a.equals(d):true
    o1==o2:false
    passString:true
    passString:false
    

    ==正在检查内存地址(引用),而.equals正在检查内容(值)

    这项工作使用StringBuffer

    public class test {
     public static void main(String[] args) {
     StringBuffer zText = new StringBuffer("");
        fillString(zText);
        System.out.println(zText.toString());
     }
      static void fillString(StringBuffer zText) {
        zText .append("foo");
    }
    }
    
    更好地使用StringBuilder

    public class test {
     public static void main(String[] args) {
     StringBuilder zText = new StringBuilder("");
        fillString(zText);
        System.out.println(zText.toString());
     }
      static void fillString(StringBuilder zText) {
        zText .append("foo");
    }
    }
    

    字符串在Java中是不可变的对象。您可以使用StringBuilder类来完成您试图完成的工作,如下所示:

    public static void main(String[] args)
    {
        StringBuilder sb = new StringBuilder("hello, world!");
        System.out.println(sb);
        foo(sb);
        System.out.println(sb);
    
    }
    
    public static void foo(StringBuilder str)
    {
        str.delete(0, str.length());
        str.append("String has been modified");
    }
    
    class MyString
    {
        public String value;
    }
    
    public static void main(String[] args)
    {
        MyString ms = new MyString();
        ms.value = "Hello, World!";
    
    }
    
    public static void foo(MyString str)
    {
        str.value = "String has been modified";
    }
    
    另一个选项是创建一个以字符串作为作用域变量的类(非常不推荐),如下所示:

    public static void main(String[] args)
    {
        StringBuilder sb = new StringBuilder("hello, world!");
        System.out.println(sb);
        foo(sb);
        System.out.println(sb);
    
    }
    
    public static void foo(StringBuilder str)
    {
        str.delete(0, str.length());
        str.append("String has been modified");
    }
    
    class MyString
    {
        public String value;
    }
    
    public static void main(String[] args)
    {
        MyString ms = new MyString();
        ms.value = "Hello, World!";
    
    }
    
    public static void foo(MyString str)
    {
        str.value = "String has been modified";
    }
    

    答案很简单。在java中,字符串是不可变的。因此,它类似于使用“final”修饰符(或C/C++中的“const”)。因此,一旦分配,您就不能像以前那样更改它

    可以更改字符串指向的值,但不能更改该字符串当前指向的实际值

    例如,
    String s1=“嘿”
    。您可以将
    s1=“woah”
    ,这是完全可以的,但实际上,您不能将字符串的基本值(在本例中为“hey”)更改为使用plusEquals等赋值后的其他值(即
    s1+=“whatup!=“hey whatup”

    为此,请使用StringBuilder或StringBuffer类或其他可变容器,然后调用.toString()将对象转换回字符串


    注意:字符串通常用作散列键,因此这是它们不可变的部分原因。

    Aaron Digulla给出了迄今为止最好的答案。他的第二个选项的一个变体是使用公共对象的包装器或容器类MutableObject