【问题标题】:Passing a String by Reference in Java?在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# 中也不起作用。

标签: java string pass-by-reference


【解决方案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】:

    您有三个选择:

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

    【讨论】:

    • 请记住 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 你写了“对象是通过引用传递的”
        【解决方案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

              【讨论】:

                猜你喜欢
                • 2010-11-21
                • 2010-12-24
                • 2018-12-07
                • 2014-08-11
                • 2017-07-25
                • 2016-07-05
                • 2013-04-07
                • 2016-04-09
                • 2018-09-01
                相关资源
                最近更新 更多