首先、我们必须牢记的一点是:java语言规范规定,String型变量指向的内存空间中的内容是不能被改变的,即String是不可改变的类!
示例一:
public class TestConstant{
public static void main(String args[]){
String str=new String("hello");
str="Welcome to Here";
System.out.println(str);
}
}
解析:毋庸置疑,此程序输出的将是“Welcome to Here”,但是给str重新赋值的操作(str="Welcome to Here"),不是简单的将str指向的原内存地址内容改为"Welcome to Here",而是从新分配一块内存用来存放"Welcome to Here",然后将str指向该新分配的内存地址。而原来的"hello"如果没有其他String变量指向它,那么他将被java的垃圾收集器回收;如果有其他的String变量指向它,它将在内存继续存在,比如:
示例二:
public class TestConstant{
public static void main(String args[]){
String str=new String("hello");
String str1=str;
str="123";
System.out.println(str);
System.out.println(str1);
}
}
解析:上面的程序会先后输出123和hello,由于str1仍然指向hello,所以hello所在内存没有被回收。
一、普通的类对象作为函数参数时
示例三:
class Common{
private int a;
public Common(int a){
this.a=a;
}
public void setA(int a){
this.a=a;
}
public void disp(){
System.out.println(a);
}
}
public class TestCommon{
public static void main(String args[]){
Common c=new Common(1);
set(c,3);
c.disp();
}
public static void set(Common cc,int a){
cc.setA(a);
}
}
解析:在执行完set(c,3)后,c的属性a变成了3。调用函数的过程如下:将句柄c的一份儿拷贝赋给参数cc,此时c和cc都指向堆中的同一块地址,即都指向同一Common对象,在set()函数中cc会试图改变Common对象的属性a,调用完函数后cc被垃圾收集器回收,而c仍然指向原来的Common对象,此时的Common对象的属性a已经发生了变化,由1变成了3。这和c语言中的指针传递很类似,读者一定要注意,此处是值传递不是引用传递,函数调用前后改变的是c指向的对象,而c作为实参在调用set()函数前后没有发生变化,仍然指向原来的Common对象。
二、String变量作为函数参数时
示例四:
public class TestString{
public static void main(String args[]){
String strTest=new String("ab");
//String strTest="ab";这两种形式的定义,输出的都是ab
setString(strTest);
System.out.println(strTest);
}
private static void setString(String str){
str=str+"c";
}
}
解析:对于java初学者来说,很多人在执行程序以前一定认为此程序和前面的示例三类似,输出的将会是"abc”,但是结果告诉我们输出的会是"ab",String在java中也被看做类,他和上面的Common类有什么区别呢?答案其实已经在开篇告诉大家了:java语言规范规定,String型变量指向的内存空间中的内容是不能被改变的。下面来详细解析一下,当我们刚开始调用setString()函数的时候,strTest句柄和str句柄同时指向了"ab”所在的内存单元,然后在setString()中试图通过str改变其对应的对象属性的值,根据String的不可改变性,此时系统会分配新的内存并令str指向该内存,该内存的内容为abc;而对于strTest他仍然指向原来的内存单元----即ab所在的内存单元,此处和示例二的道理相同。到这里读者应该明白原因了吧,由于String的不可变的性质,当你试图通过str改变其指向对象的值的时候,会分配新的空间存储新的String对象,导致str和strTest指向的地址不在相同,故程序会输出上面的结果。
三、String数组作为函数参数时
示例五:
public class TestArray{
public static void main(String[] args){
Change chge = new Change();
String[] str1 = {new String("hello"),new String("world")};
String str2 = new String("hello");
chge.change(str1,str2);
System.out.println(str1[0]);
System.out.println(str2);
}
}
class Change {
public static void change(String[] s1,String s2){
s1[0] = new String("change");
s2="change";
}
}
解析:程序输出的结果将是change和hello,对于为什么输出hello前面已经解释过了,现在解释一下String数组作为函数参数时的特点。在java中数组是被看做对象来处理的,以上面的str1[]为例,str1是指向数组对象的句柄,而str1的元素保存的是指向String对象的句柄,str1相当于C语言中指向指针的指针。上面的程序中,当我们调用change()函数的时候,首先将str1的拷贝赋给s1,str1和s1指向相同的数组对象内存空间,当我们通过s1改变数组元素的属性的时候,改变的是str1和s1共同指向的地址空间,所以函数中通过s1改变数组第一个元素处的句柄的时候会影响到str1,调用完change()函数str1指向的数组对象的第一个元素处的句柄发生了改变,指向了新的“change”字符串对象。
其实不仅仅是String数组,对于任何类型的数组,当把数组作为函数参数时,在函数中对数组元素的改变都会反映到实参上面,如下面的示例:
示例六:
public class TestArray{
public static void main(String args[]){
int a[]=new int[]{1,2,3,4};
System.out.println(a[0]);
change(a);
System.out.println(a[0]);
}
public static void change(int a1[]){
a1[0]=0;
}
}
解析;和String数组类似,上面的程序输出的会是1和0。
总结:上面说了这么多,其实关键是理解String类型的特性,以及数组在java中构成的基本原理。String非常像基本数据类型,但是他不是,他就是一个对象,但是这个对象有他本身和普通对象不同的特性。java中的数组,如果是对象数组的话其元素保存的是指向某特定类型对象的句柄,如果是基本类型的数组其元素保存的是该基本类型的值,而数组变量本身是指向数组内存的句柄,我们对数组元素进行更改时改变的是指向某个对象的句柄或基本数据类型的值。