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

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

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


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

结果是:

foo

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

这是怎么回事?

232602 次浏览

String 是不可变的。

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

字符串在 Java 中是 永恒不变

你有三个选择:

  1. 使用 StringBuilder:

    StringBuilder zText = new StringBuilder ();
    void fillString(StringBuilder zText) { zText.append ("foo"); }
    
  2. Create a container class and pass an instance of the container to your method:

    public class Container { public String data; }
    void fillString(Container c) { c.data += "foo"; }
    
  3. Create an array:

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

From a performance point of view, the StringBuilder is usually the best option.

在 Java 没有任何东西是通过引用传递的中。一切都是 通过值传递。对象引用是通过值传递的。此外,字符串是 永恒不变。所以当你追加传递的 String 时,你只是得到一个新的 String。可以使用返回值,也可以传递 StringBuffer。

正在发生的情况是,引用是通过值传递的,也就是说,传递的是引用的 收到。Java 中的任何内容都不是通过引用传递的,而且由于字符串是不可变的,所以该赋值创建了一个新的字符串对象,参考文献的副本现在指向该对象。原始引用仍然指向空字符串。

这对于任何对象都是一样的,也就是说,将它设置为方法中的一个新值。下面的示例只是让正在发生的事情变得更加明显,但是连接字符串实际上是一回事。

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

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 的解决方案——请参阅 有效的爪哇项目“ Favor immutability”

不过,正如前面提到的,StringBuilder通常会提供更好的性能——如果要做很多附加操作,特别是在循环中,那么可以使用 StringBuilder

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

字符串是 Java 中的一个特殊类。它是 Thread Safe,意思是“一旦创建了一个 String 实例,String 实例的内容将永远不会改变”。

事情是这样的

 zText += "foo";

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

对于方法 filString,您可以使用 StringBuffer 作为参数,或者您可以像下面这样修改它:

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

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

字符串不是一个基元,它是一个对象,它是对象的一种特殊情况。

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

这种方法使用 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");
}
}

字符串是 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";
}

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

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

我。String s1 = "hey".您可以创建 s1 = "woah",这完全没有问题,但是实际上您不能将字符串的底层值(在本例中为“ hey”)改为使用 plusEquals 赋值后的其他值,等等(即。s1 += " whatup != "hey whatup").

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

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

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

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

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

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

字符串 s = “ Hello”; S = s + “ hi”;

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

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

StringBuilder s = new StringBuilder () ; 附加(“ Hi”) ;

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

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

对于一个更好奇的人来说

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