当一个对象被当作参数传递到一个方法后,在此方法内可以改变这个对象的属性,那么这里到底是值传递还是引用传递?
答:是值传递。Java 语言的参数传递只有值传递。当一个实例对象作为参数被传递到方法中时,参数的值就是该对象的引用的一个副本。指向同一个对象,对象的内容可以在被调用的方法内改变,但对象的引用(不是引用的副本) 是永远不会改变的。
Java的参数传递,不管是基本数据类型还是引用类型的参数,都是按值传递,没有按引用传递!
我们可以看一下microsoft的文档中对按引用传递参数的定义(如下截图):https://docs.microsoft.com/zh-cn/dotnet/csharp/language-reference/keywords/ref#passing-an-argument-by-reference
对于参数的传递可以分为两种情况:
1.基本数据类型的参数
1 public class TransferTest { 2 public static void main(String[] args) { 3 int num = 1; 4 System.out.println("changeNum()方法调用之前:num = " + num); 5 changeNum(num); 6 System.out.println("changeNum()方法调用之后:num = " + num); 7 } 8 9 public static void changeNum(int x) { 10 x = 2; 11 } 12 }
运行结果:
传递过程的示意图如下:
分析:num作为参数传递给changeNum()方法时,是将内存空间中num所指向的那个存储单元中存放的值1复制了一份传递给了changeNum()方法中的x变量,而这个x变量也在内存空间中分配的一个存储单元。这时就把num对的值1传递给了x变量所指向的存储单元中。此后在changeNum()方法中对x变量的一切操作都是针对于x所指向的这个存储单元,与num所指向的存储单元无关。
所以,在changeNum()方法被调用后,num所指向的存储单元的值还是没有发生变化,这就是所谓的“值传递”。
值传递的精髓是:传递的是存储单元中的内容,而不是存储单元的引用。
2.引用类型的参数
示例1:
1 public class TransferTest2 { 2 public static void main(String[] args) { 3 Person person = new Person(); 4 System.out.println(person); 5 change(person); 6 System.out.println(person); 7 } 8 9 public static void change(Person p) { 10 p = new Person(); 11 } 12 } 13 14 /** 15 * Person类 16 */ 17 class Person { 18 19 }
运行结果:
可以看出两次打印结果一致。即调用change()方法后,person变量并没发生改变。
传递过程的示意图如下:
分析:
01.当程序执行到第3行 Person person = new Person()时,程序在堆内存(heap)中开辟了一块内存空间用来存储Person类实例对象,同时在栈内存(stack)中开辟了一个存储单元来存储该实例对象的引用,即上图中person指向的存储单元。
02.当程序执行到第5行 change(person)时,person作为参数(实参)传递给饿了change()方法。这里是person将自己的存储单元的内容传递给了change()方法的p变量。此后在change()方法中对p变量的一切操作都是针对于p变量所指向的存储单元,与perosn所指向的存储单元就没有关系了。
示例2:
1 public class P { 2 String name = "P"; 3 public P(String name) { 4 this.name = name; 5 } 6 @Override 7 public String toString() { 8 return name; 9 } 10 } 11 12 public class Test { 13 static P p1 = new P("p1"); 14 public static void main(String[] args) { 15 P p = new P("P"); 16 System.out.println("before change p:" + p.toString); 17 changeObj(p); 18 System.out.println("after change p:" + p.toString()); 19 } 20 21 static void changeObj(P p) { 22 p = new P("pp"); 23 System.out.println("change p:" + p.toString()); 24 //p = p1; 25 //System.out.println(p.toString()); 26 } 27 }
运行结果:
首先要理解 “=” 赋值操作的意义:
对于基本数据类型来说 “=”赋值操作是直接改变内存地址(存储单元)上的值。
对于引用类型来说 “=” 赋值操作是改变引用变量所指向的内存地址(上文中存储单元)。
调用changeObj()方法进入第21行:
执行22行后:
所以对外部的p变量是没有影响的。
总结:
函数参数传递其实是一个赋值的过程,基本类型传递的是数值,引用类型传递的引用对象的内存地址。
另外一点要特别注意,函数的参数其实是函数内部的局部变量。不要跟外部变量混淆。
示例3:
1 package cn.canshu; 2 class Person{ 3 private int id; 4 private String name; 5 public Person(int id,String name) { 6 this.id = id; 7 this.name = name; 8 } 9 } 10 public class Demo02 { 11 public static void main(String[] args) { 12 Person a = new Person(23, "a"); 13 Person b = new Person(22,"b"); 14 System.out.println("交换前 a:"+a+" b:"+b); 15 swap(a, b); 16 System.out.println("交换后 a:"+a+" b:"+b); 17 } 18 private static void swap(Person a, Person b) { 19 Person temp = a; 20 a = b; 21 b = temp; 22 } 23 }
运行结果:
同理,外部的ab引用不是内部的ad引用,故不会发生改变。
我们可以发现,Java方法的传值,实际上是把实参的值—-对象引用(对象的内存地址)传递给了形参,从而形参和实参的值(即变量里存储的内存地址,非变量本身的内存地址)是相同的,指向了同一个对象/内存地址。