java中值与引用问题的简单笔记
java中传值与传引用的问题由来已久,今天上网无意中又发现了这个问题的讨论,于是自己也复习了一下,想想还是整理一下思路,记下来供概念还有些模糊的童鞋参考参考。
关于值与引用的问题,最直观的可以用一段代码来表示:
public class Test {
public static void main(String[] args) {
String a = new String("hello");
String b = new String("hello");
if(a==b) System.out.println("true");
else System.out.println("false");
}
}
这段代码返回的是“flase”,显然可以看出来:比较(==)两个String数据的时候,并不是比较的两个数据的“值”,而是比较两个数据的“引用”是否指向同一个对象
PS.进行“比较”,也就是“==”的时候, 如果比较的是两个基本数据类型(char,byte,short,int,long,float,double,boolean),则是判断它们的值是否相等。 如果比较的不是两个基本数据类型,而是两个对象变量,则是判断它们的引用是否指向同一个对象,也就是看引用地址是否一致。 引申一下:java的基本数据类型都是传“值”的,其他的则是传“引用” (“引用”指的是内存里保存这个“值”的地址标识,本质上还是一个“值”)
所谓的“值”指的是内存里的数据值,这里是“hello”;而“引用”指的是内存里这个数据值的地址。这段代码里a与b是分别实例化的,在内存里的地址显然是不同,所以比较的结果就是“false”。
========================
这边偏一下题:创建String对象除了new,常用还有通过引号来创建,比如
public class Test {
public static void main(String[] args) {
String a = "hello";
String b = "hello";
if(a==b) System.out.println("true");
else System.out.println("false");
}
}
这中情况下返回的是“true”!这个就与JAVA虚拟机(JVM)中的字符串池有关系了。字符串池里的String对象是可以被共享使用的,因此它提高了效率。并且由于String类是final的,它的值一经创建就不可改变,因此不会因为String对象共享而带来程序的混乱。
当执行String a = "hello";的时候,JVM首先在字符串池中查找是否已经存在了值为"hello"的这么一个对象,它的判断依据是String类equals(Object obj)方法的返回值。如果字符串池中已经存在,则不再创建新的对象,直接返回已存在对象的引用;如果不存在,则先创建这个值为“hello”的对象,再把这个对象加入到字符串池中,最后将这个对象的引用返回。
因此,在这段程序中,执行String a= "hello";的时候,由于字符串池中不存在“hello”,程序创建了一个新的对象“hello”,保存到池里,并把这个对象的引用地址返回;当执行String b = "hello";的时候,由于字符串池中已经存在了刚刚创建的“hello”对象,所以直接获得了这个hello对象的引用地址,并赋值给了b。
所以这个时候,a跟b进行比较的时候,a跟b的引用地址是一样的(都是执行String a= "hello";的时候返回的那个引用地址),所以最终的比较结果是“true”。
========================
回到正题:再来看一段简单的代码
public class Test {
public static void opt(String str) {
String _str = str;
str = "world";
System.out.println(_str);
System.out.println(str);
}
public static void main(String[] args) {
String str = "hello";
opt(str);
System.out.println(str);
}
}
这段代码输出结果是:
hello world hello
结合上文可以很容易得到这个答案。简单说明下就是
1.先执行main方法里的String str = "hello";于是在字符串池里创建了一个值为“hello”的对象,并返回其引用地址 2.执行opt方法,其参数是str,也就是上一步返回的刚刚创建的那个值为“hello”的对象的引用地址 3.执行opt方法里的String _str = str; 也就是把传进来的参数(也就是那个引用地址,【注意,这只是一个副本,原先的str并没有任何改变】)赋值给_str;所以打印出来的_str为引用地址指向的那个内存空间里存的值,也就是“hello” 4.str = "world";这一步其实跟第一步一样,都是在字符串池里创建新对象,这次的对象值为“world”,返回的是这个对象的引用地址,并把这个新的引用地址赋值给str(注意,这个str是opt方法的参数,是形参,是opt方法私有的);所以打印出来的str就是“world” 5.回到mian方法,打印的str为main方法中原本定义的“hello”(这个str与上一步的不一样,是实参,是main方法内部私有的)
========================
再偏一下题:说到形参与实参之间的相互影响问题,可以简单粗糙的归纳一下:
对于“==(比较)”,“=(赋值)”来说,形参的改变对实参是没有影响的,因为这时候改变的只是引用或者值的副本(形参本身),实参不发生改变 而对于“.(操作)”来说,形参的改变会直接改变形参(引用地址)所指向值,这就导致实参的值发生了变化
比如下面这段代码:
public class Test {
public static void main(String[] args) {
StringBuffer a=new StringBuffer ("A");
StringBuffer b=new StringBuffer ("B");
oper(a,b);
System.out.print(a+","+b);
}
static void oper(StringBuffer c,StringBuffer d){
c.append("C");
d=c;
d.append("D");
}
}
输出结果是什么呢?简单分析一下:
1.分别创建值为"A","B"的对象a,b,分别返回对象a,b的引用 2.执行oper方法,将第一步返回的引用地址作为参数。此时oper方法中的参数c跟d(形参)的值就分别是a跟b的引用地址(当然这边是个副本) 3.执行“.append”操作,注意这边是“.”,这个操作直接在形参c所指向的那个值的后面加上一个“C”字符串。此时,c的值是一个引用地址,这个引用跟a的引用是一样的,所以c所指向的值跟a所指向的值是同一个,所以这一步操作就相当于把对象a的值变成了“AC” 4.继续执行d=c;这一步操作把c的引用赋值给d,此时d实际指向的值跟a指向的值也是同一个 5.再执行 d.append("D"); 之后,对象a的值再次被改变,成为“ACD” 6.所以最终的打印结果是“ACD,B”
有一点要注意的是,如果不是StringBuffer而是String的话,结果又不一样了。比如说:
public class Test {
public static void main(String[] args) {
String a=new String ("A");
String b=new String ("B");
oper(a,b);
System.out.print(a+","+b);
}
static void oper(String c,String d){
c.concat("C");
d=c;
d.concat("D");
}
}
这个的输出结果为“A,B”。这是因为java里规定了String值的不可被改变,所以随便怎么折腾,a,b的值在创建的时候已经被固定下来了,没法再改了
后记:
本想随便写几句,没想到巴拉巴拉说了一大通,不过也借此把自己的思路缕了一遍,正好复习java基础知识,哈哈!
有说的不对的地方请务必指正!!!拜谢~~^_^