【Java】 参数的传递:值传递与引用传递讨论
内容稍多,可直接看第4点的讨论结果
前言
在涉及到传递参数给方法时,容易出现一些参数传递错误的问题,这就涉及到了参数的传递问题,必须搞清楚:参数是如何传递到方法中的?一般来说,参数的传递可以分为两种:值传递和引用传递。
所谓值传递,就是方法中的形参获得的是实参的值,而引用传递,就是说方法中的形参获得的是实参的引用(地址)。
参数的传递其实类似于一个赋值操作,所以接下来,先讨论值和地址的问题,再讨论赋值操作,最后才进行参数传递的讨论。
1.基本概念
首先要搞清楚一个概念,即:变量中储存的内容是什么。
变量类型分为基本数据类型和引用类型:
基本数据类型的变量直接存储的是变量的值,如int i=10; i中存储的为变量的值。
引用类型的变量存储的则是实际对象的地址,如Dog myDog = new Dog(); myDog中存储的仅仅是地址(也可以称为引用)。
2.赋值操作
需要明确赋值操作的含义。
赋值操作“=”包含两个意思:1.放弃了原有的值或引用;2.得到了 = 右侧变量的值或引用。
基本类型:= 操作代表完整复制了变量的值。例如:int a=b; a仅获取了b的值,两者在此之后并无任何关系。
引用类型:= 操作代表复制了变量的引用。 例如:Dog aDog=bDog; aDog复制了bDog的引用,两个变量都指向同一个Dog对象。
一个简单的例子:
public class Test { public static void main(String[] args) { int x=10; int y=x; x=20; System.out.println("x:"+x); System.out.println("y:"+y); Dog aDog=new Dog(); aDog.name="阿黄"; aDog.age=1; Dog bDog=aDog; aDog.name="旺财";//改变aDog的名字 bDog.age=2;//改变bDog的年龄 System.out.println("aDog:"+aDog.name+","+aDog.age); System.out.println("bDog:"+bDog.name+","+bDog.age); } } class Dog { String name; int age; }
x:20 y:10 aDog:旺财,2 bDog:旺财,2
由结果可以看出,对于基本数据类型,在y=x后,y仅仅是获得了x的值,两者不再有关系了,因此输出结果不同;
对于引用类型,在bDog=aDog后,bDog获取了aDog的地址,两个变量都是指的同一条狗。虽然分别变化了aDog的名字和bDog的年龄,但其实变的都是同一个对象,所以输出结果相同。
3.方法中的参数传递讨论
方法中参数传递的本质即为赋值操作。
/*第一个例子:基本类型*/ void foo(int value) { value = 100; } foo(num); // num 没有被改变
例一分析:参数传递,相当于执行了value=num,value获得了num的值,这之后value与num无关,所以num没有改变。
/*第二个例子:没有提供改变自身方法的引用类型*/ void foo(String text) { text = "windows"; } foo(str); // str 也没有被改变
例二分析:参数传递后,text与str都存储了相同的地址,指向同一个对象;但之后text重新进行了赋值操作text= "windows",text放弃了原有的引用而指向了新的对象(见上述赋值操作的两重意思),而str仍为原有的引用,所以str没有改变。
/*第三个例子:提供了改变自身方法的引用类型*/ StringBuilder sb = new StringBuilder("iphone"); void foo(StringBuilder builder) { builder.append("4"); } foo(sb); // sb 被改变了,变成了"iphone4"。
例三分析:参数传递后,sb与builder指向同一个对象,builder.append()仅对该对象进行了增加操作,sb和builder仍指向同一对象,所以sb变为了"iphone4"。
参数传递后:
执行builder.append("4")后:
/*第四个例子:提供了改变自身方法的引用类型,但是不使用,而是使用赋值运算符。*/ StringBuilder sb = new StringBuilder("iphone"); void foo(StringBuilder builder) { builder = new StringBuilder("ipad"); } foo(sb); // sb 没有被改变,还是 "iphone"。
例四分析:参数传递后,sb与builder指向同一个对象,但之后builder重新进行了赋值操作builder = new StringBuilder("ipad"),builder指向了一个新的对象,而sb仍为指向原来的对象,所以sb没有改变。
参数传递后:
执行builder = new StringBuilder("ipad")后:
4.值传递与引用传递讨论结果
在上述讨论中,可以知道,参数传递相当于赋值操作,
对于基本数据类型,方法中的形参获取了实参的值,即可以理解为值传递;
对于引用类型,方法中的形参获取的是实参的引用,两者指向同一对象,即可以理解为引用传递。
值传递中,形参的改变不影响实参;引用传递中,形参让对象改变,相当于让实参也发生了改变。
也有人认为,Java中的参数传递只有值传递,我觉得应该是把“引用传递”认为是引用的地址值的传递,所以统称为值传递。(知识还很浅薄,只是个人理解)
5.再来两个例子
基本数据类型比较简单,不再赘述。引用类型的例子再来两个,读者可自行分析
public class Test { private void test1(A a) { a.age = 20; System.out.println("test1方法中的age=" + a.age); } public static void main(String[] args) { Test t = new Test(); A a = new A(); a.age = 10; t.test1(a); System.out.println("main方法中的age=" + a.age); } } class A { public int age = 0; }
test1方法中的age=20
main方法中的age=20
public class Test { private void test2(A a) { a = new A();// 新加的一行 a.age = 20; System.out.println("test2方法中的age=" + a.age); } public static void main(String[] args) { Test t = new Test(); A a = new A(); a.age = 10; t.test2(a); System.out.println("main方法中的age=" + a.age); } } class A { public int age = 0; }
test2方法中的age=20
main方法中的age=10
提示:test2中新加的一行使形参重新指向了一个新的对象,所以之后操作就和主程序中的实参无关了。
6.String类型和数组的参数传递
String类型和数组的参数传递比较容易让人困惑,后面又看到相关的文章,这里再讨论一下
public class Test { public void arrayPassTest(String s, String[] ss) { s = "bad"; ss[0] = "bbb"; } public static void main(String[] args) { String s1 = new String("good"); String[] ss1 = { "aaa" }; // string数组,只有一个元素 Test test = new Test(); test.arrayPassTest(s1, ss1); System.out.println(s1 + ss1[0]); } }
goodbbb
对于String类型,
很多人将String类型的参数传递理解为值传递(类似于基本数据类型),但其实String 类型的传递与其他引用类型相同,仍然是引用传递,也即是地址传递。
既然是引用传递,那为什么s1不随s改变呢?在方法arrayPassTest()中,s="bad"其实相当于是s=new String("bad"),s指向了一个新的String对象,与实参s1不再是同一个对象了,所以s1不变。(明白new String(),将String类型和其他引用类型一样理解就可以了。)
对于数组,
数组也是一种引用类型,ss和ss1指的是同一个数组对象,ss[0]="bbb"只是将该数组对象中的第一个元素改变了,并没有新的数组对象产生。ss和ss1指向的数组对象始终都是同一个,所以ss1发生了改变。
7.参考来源
《Head First Java》
本文内容是根据参考内容 结合自己的个人理解写的,如有错误,请多多指正。