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

时间:2009-08-13 08:25:55

标签: java c string pass-by-reference pass-by-value

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

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

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

输出是:

foo

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

这里发生了什么?

15 个答案:

答案 0 :(得分:179)

您有三种选择:

  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"; }
    
  4. 从性能的角度来看,StringBuilder通常是最好的选择。

答案 1 :(得分:80)

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

答案 2 :(得分:48)

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

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

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

答案 3 :(得分:18)

java.lang.String是不可变的。

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

答案 4 :(得分:7)

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

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

/ * OUTPUT * /

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

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

答案 5 :(得分:6)

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

作业

zText += foo;

相当于:

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

也就是说,它(本地)将参数zText重新指定为新引用,该引用指向新的内存位置,其中包含{{1}的原始内容的新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);
  }
}

这可能是一般情况下最类似Java的解决方案 - 请参阅 Effective Java 项“赞成不变性。”

如上所述,Original value: Local value: foo Final value: foo 通常会为您提供更好的效果 - 如果您有很多要做的事情,特别是在循环内,请使用StringBuilder

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

答案 6 :(得分: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");
}

另一种选择是使用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";
}

答案 7 :(得分: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";
}

答案 8 :(得分:2)

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

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

IE中。 String s1 = "hey"。你可以制作s1 = "woah",这是完全正常的,但你实际上不能将字符串的基础值(在这种情况下:“嘿”)更改为使用plusEquals等分配的其他东西(即。s1 += " whatup != "hey whatup")。

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

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

答案 9 :(得分:1)

Java中的字符串是immutable

答案 10 :(得分: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");
}
}

答案 11 :(得分:1)

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

String s =" Hello&#34 ;; S = S +"喜&#34 ;;

这里先前的引用被替换为指向值" HelloHi"的新引用。

但是,为了实现可变性,我们有StringBuilder和StringBuffer。

StringBuilder s = new StringBuilder(); s.append("您好&#34);

这会追加新值"嗨"同样的参考。 //

答案 12 :(得分:0)

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

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

您保存容器类的声明。缺点是依赖于commons lang lib。但是lib有很多有用的功能,而且我曾经使用过的任何大型项目都使用它。

答案 13 :(得分:0)

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

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

答案 14 :(得分: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如何通过String变量s进行更改。   更改s时,在函数changeto中为变量s分配了一个新对象