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

Sor*_*son 150 java string pass-by-reference

我习惯于在以下方面做以下事情C:

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

void fillString(String zText) {
    zText += "foo";
}
Run Code Online (Sandbox Code Playgroud)

输出是:

foo
Run Code Online (Sandbox Code Playgroud)

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

这里发生了什么?

Aar*_*lla 189

你有三个选择:

  1. 使用StringBuilder:

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

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

    new String[] zText = new String[1];
    zText[0] = "";
    
    void fillString(String[] zText) { zText[0] += "foo"; }
    
    Run Code Online (Sandbox Code Playgroud)

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

  • @Boris Pavlovic - 是的,但除非相同的StringBuilder被不同的线程使用,IMO在任何给定场景中都不太可能,它应该不是问题.如果该方法由不同的线程调用,接收不同的StringBuilder则无关紧要. (11认同)
  • 请记住,StringBuilder不是线程安全的.在多线程环境中,使用StringBuffer类或手动处理同步. (10认同)

zed*_*doo 82

在Java中没有任何内容通过引用传递.一切都是通过价值传递的.对象引用按值传递.另外字符串是不可变的.因此,当您附加到传递的String时,您只需获得一个新的String.您可以使用返回值,或者传递StringBuffer.

  • 嗯,不,你是通过引用传递一个对象是一个误解.您正在按值传递引用. (39认同)
  • 是的,这是一种误解.这是一个巨大而广泛的误解.这导致我讨厌的一个采访问题:("Java如何传递参数").我讨厌它,因为大约一半的采访者实际上似乎想要错误的答案("基于价值的原语,通过引用的对象").正确的答案需要更长的时间,而且似乎会混淆其中一些.并且他们不会被说服:我发誓我在技术屏幕上不及格,因为CSMajor型筛选者在大学里听到了误解,并认为它是福音.FEH. (27认同)
  • 一切都以所有语言的价值传递.其中一些值恰好是地址(引用). (5认同)
  • @CPerkins你不知道我有多生气.它让我的血液沸腾. (3认同)
  • 这是一个完全迂腐的观点,还是我错过了什么?我不明白这个答案的赞成票。它只是混淆了问题。 (3认同)
  • 这不是误解,而是一个概念 (2认同)

Ed *_* S. 50

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

这对任何对象都是相同的,即在方法中将其设置为新值.下面的例子只是让事情变得更加明显,但连接一个字符串实际上是一回事.

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

  • 很好的例子! (6认同)

Rya*_*des 18

java.lang.String是不可变的.

我讨厌粘贴URL,但https://docs.oracle.com/javase/10/docs/api/java/lang/String.html对于您阅读和理解是否在java-land中是必不可少的.


jan*_*ith 7

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

字符串不是基元,它是一个对象,它是对象的特例.

这是为了节省内存.在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));
    }
}
Run Code Online (Sandbox Code Playgroud)

/*OUTPUT*/

a==b:true
a==c:true
a==d:false
a.equals(d):true
o1==o2:false
passString:true
passString:false
Run Code Online (Sandbox Code Playgroud)

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

  • 不完全正确.Java总是通过VALUE; 诀窍是对象通过*reference*传递,它们通过值传递.(狡猾,我知道).检查一下:http://javadude.com/articles/passbyvalue.htm (3认同)
  • @adhg 你写的“对象通过引用传递,而通过值传递。” <---那是胡言乱语。您的意思是对象具有按值传递的引用 (2认同)
  • -1您写道"对象通过引用传递",< - FALSE.如果这是真的那么当调用方法并传递变量时,该方法可以使变量指向/引用不同的对象,但它不能.一切都是通过java中的值传递的.并且您仍然可以看到在方法之外更改对象属性的效果. (2认同)

Dav*_*les 6

Java中的所有参数都是按值传递的.将a传递String给函数时,传递的值对String对象的引用,但您无法修改该引用,并且基础String对象是不可变的.

分配

zText += foo;
Run Code Online (Sandbox Code Playgroud)

相当于:

zText = new String(zText + "foo");
Run Code Online (Sandbox Code Playgroud)

即,(局部地)重新指定参数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);
  }
}
Run Code Online (Sandbox Code Playgroud)

打印:

Original value:
Local value: foo
Final value:
Run Code Online (Sandbox Code Playgroud)

如果你想修改字符串,你可以使用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);
  }
}
Run Code Online (Sandbox Code Playgroud)

打印:

Original value:
Local value: foo
Final value: foo
Run Code Online (Sandbox Code Playgroud)

这可能是一般情况下最类似Java的解决方案 - 请参阅Effective Java项目"Favor immutability".

但是,如前所述,StringBuilder通常会为您提供更好的性能 - 如果您有很多需要做的事情,特别是在循环内部,请使用StringBuilder.

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

  • 你说"严格来说"就好像它几乎无关紧要 - 而它却是问题的核心.如果Java*真的*通过引用传递,那就不会有问题了.*请*尽量避免传播"Java通过引用传递对象"的神话 - 解释(正确)"引用按值传递"模型更有帮助,IMO. (6认同)
  • 很公平。作为一名 Java 程序员,我已经有十多年没有看到任何“实际上”通过引用传递的东西了,所以我的语言变得很草率。但你是对的,我应该更加小心,尤其是对于 C 编程受众。 (2认同)

Fad*_*ass 5

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");
}
Run Code Online (Sandbox Code Playgroud)

另一个选择是创建一个类,其中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";
}
Run Code Online (Sandbox Code Playgroud)

  • "不可变对象"是我打算说的.我出于某种原因,在@VWeber发表评论后没有重新阅读我的答案,但现在我看到了他想说的话.我的错.回答修改 (2认同)