179

我习惯于执行以下操作C

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

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

输出是:

foo

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

这里发生了什么?

4

15 回答 15

215

你有三个选择:

  1. 使用 StringBuilder:

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

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

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

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

于 2009-08-13T08:30:08.380 回答
92

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

于 2009-08-13T08:30:46.583 回答
55

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

这对于任何对象都是一样的,即在方法中将其设置为新值。下面的示例只是使发生的事情更加明显,但连接字符串实际上是一回事。

void foo( object o )
{
    o = new Object( );  // original reference still points to old value on the heap
}
于 2009-08-13T08:32:24.257 回答
18

java.lang.String 是不可变的。

我讨厌粘贴 URL,但https://docs.oracle.com/javase/10/docs/api/java/lang/String.html对于您在 Java 领域的阅读和理解至关重要。

于 2009-08-13T08:26:57.200 回答
11

Java 中的所有参数都是按值传递的。当您将 a 传递String给函数时,传递的值对 String 对象的引用,但您不能修改该引用,并且底层 String 对象是不可变的。

那作业

zText += foo;

相当于:

zText = new String(zText + "foo");

也就是说,它(本地)将参数重新分配zText为新的引用,它指向一个新的内存位置,其中是一个新的,String包含附加的原始内容。zText"foo"

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

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

印刷:

Original value:
Local value: foo
Final value:

如果您想修改字符串,您可以如前所述使用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: foo

在一般情况下,这可能是最类似于 Java 的解决方案——请参阅有效 Java项目“支持不变性”。

但是,如上所述,它StringBuilder通常会给您带来更好的性能——如果您有很多附加操作要做,尤其是在循环内,请使用StringBuilder.

但是如果可以的话,尝试传递不可变Strings而不是可变StringBuilders的——你的代码将更容易阅读和维护。考虑制作您的参数final,并配置您的 IDE,以便在您将方法参数重新分配给新值时发出警告。

于 2009-08-13T08:43:12.190 回答
8

对象通过引用传递,基元通过值传递。

String 不是原始的,它是一个对象,它是对象的一个​​特例。

这是为了节省内存。在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 正在检查内容(值)

于 2009-08-13T23:55:53.547 回答
6

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

另一种选择是使用 String 作为范围变量创建一个类(强烈建议不要这样做),如下所示:

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";
}
于 2013-06-30T23:27:37.090 回答
2

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

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

IE。String s1 = "hey". 您可以 make s1 = "woah",这完全没问题,但是一旦使用 plusEquals 等(即。s1 += " whatup != "hey whatup")分配字符串,您实际上不能将字符串的基础值(在本例中:“hey”)更改为其他值。

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

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

于 2014-04-07T19:09:11.367 回答
2

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

这是发生了什么

 zText += "foo";

首先,Java 编译器将获取 zText String 实例的值,然后创建一个新的 String 实例,其值为 zText 附加“foo”。所以你知道为什么 zText 指向的实例没有改变。这完全是一个新的例子。事实上,甚至 String "foo" 也是一个新的 String 实例。所以,对于这条语句,Java会创建两个String实例,一个是“foo”,另一个是zText的值追加“foo”。规则很简单:String 实例的值永远不会改变。

对于方法 fillString,您可以使用 StringBuffer 作为参数,也可以像这样更改它:

String fillString(String zText) {
    return zText += "foo";
}
于 2009-08-13T09:23:23.140 回答
1

这个作品使用 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");
}
}
于 2013-05-20T14:07:25.723 回答
1

字符串在 Java中是不可变的。

于 2009-08-13T08:29:00.317 回答
1

字符串在java中是不可变的。您不能修改/更改现有的字符串文字/对象。

字符串 s="你好"; s=s+“嗨”;

在这里,以前的引用 s 被新的引用 s 替换,指向值“HelloHi”。

然而,为了带来可变性,我们有 StringBuilder 和 StringBuffer。

StringBuilder s=new StringBuilder(); s.append("嗨");

这会将新值“Hi”附加到相同的引用 s。//

于 2017-09-25T07:23:55.260 回答
0

到目前为止,Aaron Digulla 给出了最好的答案。他的第二个选项的一个变体是使用 commons lang 库版本 3+ 的包装器或容器类 MutableObject:

void fillString(MutableObject<String> c) { c.setValue(c.getValue() + "foo"); }

您保存容器类的声明。缺点是对公共语言库的依赖。但是这个库有很多有用的功能,我从事的几乎所有大型项目都使用了它。

于 2014-11-23T21:58:04.653 回答
0

对于更好奇的人

class Testt {
    static void Display(String s , String varname){
        System.out.println(varname + " variable data = "+ s + " :: address hashmap =  " + s.hashCode());
    }

    static void changeto(String s , String t){
        System.out.println("entered function");
        Display(s , "s");
        s = t ;
        Display(s,"s");
        System.out.println("exiting function");
    }

    public static void main(String args[]){
        String s =  "hi" ;
        Display(s,"s");
        changeto(s,"bye");
        Display(s,"s");
    }
}

现在通过运行上面的代码,您可以看到地址如何hashcodes随字符串变量 s 变化。changeto当 s 改变时,一个新对象被分配给函数中的变量 s

于 2019-05-23T11:40:55.883 回答
0

为了在 java 中通过引用传递对象(包括字符串),您可以将其作为周围适配器的成员传递。一个通用的解决方案在这里:

import java.io.Serializable;

public class ByRef<T extends Object> implements Serializable
{
    private static final long serialVersionUID = 6310102145974374589L;

    T v;

    public ByRef(T v)
    {
        this.v = v;
    }

    public ByRef()
    {
        v = null;
    }

    public void set(T nv)
    {
        v = nv;
    }

    public T get()
    {
        return v;
    }

// ------------------------------------------------------------------

    static void fillString(ByRef<String> zText)
    {
        zText.set(zText.get() + "foo");
    }

    public static void main(String args[])
    {
        final ByRef<String> zText = new ByRef<String>(new String(""));
        fillString(zText);
        System.out.println(zText.get());
    }
}
于 2018-09-02T11:45:48.297 回答