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

Posted

技术标签:

【中文标题】在Java中通过引用传递字符串?【英文标题】:Passing a String by Reference in Java? 【发布时间】:2010-11-19 05:41:24 【问题描述】:

我习惯在C做以下事情:

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


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

输出是:

foo

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

这是怎么回事?

【问题讨论】:

即使它们是通过引用传递的(在 Java 中传递的是引用值的副本,但那是另一个线程)字符串对象是不可变的,所以无论如何都不会工作 这不是 C 代码。 @Phil:这在 C# 中也不起作用。 【参考方案1】:

对于好奇的人

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 变化。 当 s 改变时,一个新对象被分配给函数 changeto 中的变量 s

【讨论】:

【参考方案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";

【讨论】:

...虽然如果您要按照此代码定义“fillString”,您真的应该给它一个更合适的名称! 是的。这个方法应该命名为“appendString”左右。【参考方案3】:

您有三个选择:

    使用 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 通常是最好的选择。

【讨论】:

请记住 StringBuilder 不是线程安全的。在多线程环境中使用 StringBuffer 类或手动处理同步。 @Boris Pavlovic - 是的,但除非不同的线程使用相同的 StringBuilder,在任何给定的情况下,IMO 都不太可能,这应该不是问题。方法是否被不同的线程调用,接收不同的StringBuilder也没关系。 我最喜欢第三个选项(数组),因为它是唯一通用的。它还允许传递布尔值、整数等。您能否详细解释一下此解决方案的性能 - 您声称从性能角度来看第一个更好。 @meolic StringBuilders += "..." 更快,因为它不会每次都分配新的 String 对象。事实上,当你编写像s = "Hello " + name 这样的Java 代码时,编译器会生成一个字节码,它会创建一个StringBuilder 并调用append() 两次。【参考方案4】:

Java 中的所有参数都是按值传递的。当您将 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 的解决方案 - 请参阅 Effective Java 项“支持不变性”。

不过,如前所述,StringBuilder 通常会为您提供更好的性能 - 如果您有很多附加操作要做,尤其是在循环内,请使用 StringBuilder

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

【讨论】:

您说“严格来说”好像它几乎无关紧要 - 而它是问题的核心。如果Java 真的 有通过引用,那将不是问题。 尽量避免传播“Java 通过引用传递对象”的神话 - 解释(正确的)“引用通过值传递”模型更有帮助,IMO。 嘿,我之前的评论去哪儿了? :-) 正如我之前所说,正如 Jon 现在所补充的,这里真正的问题是引用是按值传递的。这很重要,很多人不理解语义差异。 很公平。作为一名 Java 程序员,我在十多年的时间里没有看到任何实际上通过引用传递的东西,所以我的语言变得草率了。但你说得对,我应该更加小心,尤其是对于 C 编程的观众。【参考方案5】:

为了在 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());
    

【讨论】:

【参考方案6】:

java.lang.String 是不可变的。

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

【讨论】:

我不认为 Soren Johnson 不知道 String 是什么,这不是问题,也不是我在使用 Java 15 年后来这里寻找的东西。【参考方案7】:

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

字符串 s="你好"; s=s+"你好";

这里之前的引用 s 被新的引用 s 替换,指向值 "HelloHi"。

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

StringBuilder s=new StringBuilder(); s.append("你好");

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

【讨论】:

【参考方案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 正在检查内容(值)

【讨论】:

不完全正确。 Java 总是按 VALUE 传递;诀窍是对象是由 reference 传递的,它是按值传递的。 (棘手,我知道)。检查这个:javadude.com/articles/passbyvalue.htm @adhg 你写的“对象是通过引用传递的,它是通过值传递的。” -1 你写了“对象是通过引用传递的” 在这里查看答案***.com/questions/40480/…【参考方案9】:

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

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

即。 String s1 = "hey"。您可以设置s1 = "woah",这完全没问题,但是一旦使用 plusEquals 等(即@ 987654323@)。

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

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

【讨论】:

它是可变的还是不可变的并不相关。引用上的= 永远不会影响它曾经指向的对象,无论类如何。【参考方案10】:

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

【讨论】:

嗯..不,您通过引用传递对象是一种误解。您正在按值传递引用。 是的,这是一种误解。这是一个巨大的、普遍的误解。它引出了一个我讨厌的面试问题:(“Java 如何传​​递参数”)。我讨厌它,因为大约一半的面试官实际上似乎想要错误的答案(“原始价值,对象引用”)。正确答案需要更长的时间才能给出,而且似乎使他们中的一些人感到困惑。他们不会被说服:我发誓我没有通过技术筛选,因为 CSMajor 类型的筛选器在大学里听到了这种误解,并相信它是福音。费。 @CPerkins 你不知道这让我有多生气。这让我热血沸腾。 所有语言中的一切都是按值传递的。其中一些值恰好是地址(引用)。 这完全是迂腐的观点,还是我遗漏了什么?我不明白这个答案的赞成票。它只是混淆了这个问题。【参考方案11】:

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

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

你保存容器类的声明。缺点是依赖于 commons lang lib。但是这个 lib 有很多有用的功能,我从事的几乎所有大型项目都使用它。

【讨论】:

【参考方案12】:

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";

【讨论】:

String 不是 Java 中的原始类型。 没错,但你到底想把我的注意力吸引到什么地方? 自从Java String 是原始的?!所以!通过引用传递的字符串。 '不可变对象'是我想说的。出于某种原因,我在@VWeber 发表评论后没有重新阅读我的答案,但现在我明白了他想说什么。我的错。答案已修改【参考方案13】:

这个作品使用 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");


【讨论】:

【参考方案14】:

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

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

void foo( object o )

    o = new Object( );  // original reference still points to old value on the heap

【讨论】:

【参考方案15】:

Java 中的字符串是immutable。

【讨论】:

以上是关于在Java中通过引用传递字符串?的主要内容,如果未能解决你的问题,请参考以下文章

无法在 MySQLi 中通过引用传递参数 [重复]

在 C++ 中通过引用和值传递字符串

在 C++ 中通过引用传递 char 的效率

如何在php中通过引用传递无限参数[重复]

在 C 中通过引用传递字符串

如何在 Turbo C/C++ 中通过引用传递字符串