关于Java中String的传递问题

  1. 今天在写java时想写一个方法public void Helper(TreeNode root,List<String> list, String path) 前面两者不必说,传递的是地址,叫 emm 叫值传递——java中只存在值传递,只存在值传递!!! (然而我们经常看到对于对象(数组,类,接口)的传递似乎有点像引用传递,可以改变对象中某个属性的值。但是不要被这个假象所蒙蔽,实际上这个传入函数的值是对象引用的拷贝,即传递的是引用的地址值,所以还是按值传递。) 再来看一看String的问题,String是对象,但是表现出像基本数据类型一样的值传递表现,比较一下下面两者:
1
2
3
4
5
6
7
8
9
10
11
public class Test3 {
public static void change(int []a){
a[0]=50;
}
public static void main(String[] args) {
int []a={10,20};
System.out.println(a[0]);
change(a);
System.out.println(a[0]);
}
}

输出10,50

1
2
3
4
5
6
7
8
9
10
11
12
public class Test {
public static void change(String s){
s="zhangsan";
}

public static void main(String[] args) {
String s=new String("lisi");
System.out.println(s);
change(s);
System.out.println(s);
}
}

输出lisi lisi
照理说String是对象,应该输出lisi zhangsan才对啊,怎么change方法里没把String改掉呢?这是因为String具有不变性,看下图 来源 这里写图片描述
String具有不可变性,String被重新赋值为abcdel时,是在堆内重新开辟空间放入abcdel,并且String指向它,相当于新建了一个String对象,但是原先的String s=abcd仍然存在。
同理对于上面change方法里重新建立了一个String zhangsan对象,回到main里,zhangsan对象地址由于是值传递,在main里当然就消失了,此时的s仍然是lisi这个对象.
2. 再从根本上来看下为什么String具有这种不变性
翻开JDK源码,java.lang.String类起手前三行,是这样写的:

1
2
3
4
5
6
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
/** String本质是个char数组. 而且用final关键字修饰.*/
private final char value[];
...
...
}

首先String类是用final关键字修饰,这说明String不可继承。
再看下面,String类的主力成员字段value是个char[ ]数组,而且是用final修饰的。final修饰的字段创建以后就不可改变。

有的人以为故事就这样完了,其实没有。因为虽然value是不可变,也只是value这个引用地址不可变。挡不住Array数组是可变的事实。Array的数据结构看下图,

这里写图片描述

也就是说Array变量只是stack上的一个引用,数组的本体结构在heap堆。String类里的value用final修饰,只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。看下面这个例子,

1
2
3
4
5
6
7
final int[] value={1,2,3}
int[] another={4,5,6};
value=another; //编译器报错,final不可变
value用final修饰,编译器不允许我把value指向堆区另一个地址。但如果我直接对数组元素动手,分分钟搞定。

final int[] value={1,2,3};
value[2]=100; //这时候数组里已经是{1,2,100}

所以String是不可变,关键是因为SUN公司的工程师,在后面所有String的方法里很小心的没有去动Array里的元素,没有暴露内部成员字段。private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承,避免被其他人继承后破坏。所以String是不可变的关键都在底层的实现,而不是一个final。考验的是工程师构造数据类型,封装数据的功力。

参考文献

[1] 胖胖. 如何理解 String 类型值的不可变[EB/OL]. https://www.zhihu.com/question/20618891.