交换地址或者值不生效
读者如果对栈和堆的概念理解较深,可以直接滑到底部看总结
传参其实是使得形参得到一份实参副本(形参收到的传参),实质是在内存中新开辟了一片空间,而并非传参获得引用,如果传递的是基本数据类型,则传递值的copy
class Student
{
int age;
public Student(int age)
{
this.age = age;
}
}
public class JavaIsAboutPassingByValue
{
// 基本数据类型会直接复制一份到方法栈中
public static void swapValue(int age1, int age2)
{
int temp = age1;
age1 = age2;
age2 = temp;
}
public static void main(String[] args)
{
Student stu1 = new Student(3);
Student stu2 = new Student(7);
System.out.println("stu1 " + stu1.age + " stu2 " + stu2.age);
swapValue(stu1.age, stu2.age);
System.out.println("stu1 " + stu1.age + " stu2 " + stu2.age);
}// output:
// stu1 3 stu2 7
// stu1 3 stu2 7
}
如果传递的是引用数据类型,则传递的是地址值的copy(新建了一个引用指向对象,并非获得实参引用或对象),所以方法内进行交换操作,在方法内的新建引用确实进行了交换,通过交换的引用看到引用的指向发生了交换,但方法出栈之后,新建的引用随即销毁。
public class JavaIsAboutPassingByValue
{
// 实质是交换 s1,s2 两个复制来的引用指向,而不是交换s1,s2两个对象的地址值
public static void swapReference(Student s1, Student s2)
{
// 此处为了区别实参stu1,stu2,我将形参命名为s1,s2
// 可以更清楚地看清是形参与实参是不同的引用,尽管它们指向同一个地址
Student temp = s1;
s1 = s2;
s2 = temp;
// 方法出栈,销毁s1,s2
}
public static void main(String[] args)
{
Student stu1 = new Student(3);
Student stu2 = new Student(7);
System.out.println("stu1 " + stu1 + " stu2 " + stu2);
swapReference(stu1, stu2);
System.out.println("stu1 " + stu1 + " stu2 " + stu2);
}// output:
// stu1 Student@1b6d3586 stu2 Student@4554617c
// stu1 Student@1b6d3586 stu2 Student@4554617c
}
这个过程中,对象的地址没有发生改变,交换的一直都是引用指向(ex: 有两根电线杆,甲绑了两根绳子,甲1->杆1,甲2->杆2,然后乙通过甲也知道了电线杆位置,也绑了两根绳子,乙1->杆1,乙2->杆2,乙自己将绳子交换,乙2->杆1,乙1->杆2,不影响电线杆位置和甲的指向)
虽然很废话,但是可以让人更容易理解为什么java在方法内操作传参的值进行交换不生效。
Java本质是值传递,方法传参只能得到实参的copy版,方法内对实参进行修改不影响实参值。但通过获得引用,更改引用的引用指向(也有可能是引用的引用里的内容,即值),可以修改引用的实际值。(不修改引用地址,但因为自身也指向引用所指向的对象,所以修改对象可以使得引用的结果也发生变化)
public class JavaIsAboutPassingByValue
{
// 通过传参,自身获得地址值,修改对象内的内容(值)
// 从setValue方法外看,好像也达成了修改的效果
// 和上面swapValue方法不同的地方在于,此处传参传的是Student引用数据类型
public static void setValue(Student s1, Student s2)
{
int temp = s1.age;
s1.age = s2.age;
s2.age = temp;
}
public static void main(String[] args)
{
Student stu1 = new Student(3);
Student stu2 = new Student(7);
System.out.println("stu1 " + stu1.age + " stu2 " + stu2.age);
setValue(stu1, stu2);
System.out.println("stu1 " + stu1.age + " stu2 " + stu2.age);
}// output:
// stu1 3 stu2 7
// stu1 7 stu2 3
}
下面是交换引用的引用,即地址值
class Student
{
String name;
int age;
Pet pet;
public Student(String name, int age, Pet pet)
{
this.name = name;
this.age = age;
this.pet = pet;
}
@Override
public String toString()
{
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
", pet=" + pet +
'}';
}
}
class Pet
{
String pet;
public Pet(String pet)
{
this.pet = pet;
}
@Override
public String toString()
{
return "Pet{" +
"pet='" + pet + '\'' +
'}';
}
}
public class JavaIsAboutPassingByValue
{
public static void swapPet(Student s1, Student s2)
{
Pet temp = s1.pet;
s1.pet = s2.pet;
s2.pet = temp;
}
public static void main(String[] args)
{
Student stu1 = new Student("太一", 13, new Pet("亚古兽"));
Student stu2 = new Student("嘉儿", 11, new Pet("仙女兽"));
System.out.println(stu1 + "\n" + stu2);
swapPet(stu1, stu2);
System.out.println("--------------------------------------------------");
System.out.println(stu1 + "\n" + stu2);
}// output:
// Student{name='太一', age=11, pet=Pet{pet='亚古兽'}}
// Student{name='嘉儿', age=9, pet=Pet{pet='仙女兽'}}
// --------------------------------------------------
// Student{name='太一', age=11, pet=Pet{pet='仙女兽'}}
// Student{name='嘉儿', age=9, pet=Pet{pet='亚古兽'}}
上述分为四种情况:
- 传参类型为基本数据类型(int,char,byte,等 ),方法内交换值不生效
- 传参类型为引用数据类型,方法内交换引用类型(传参对象的地址值)不生效
- 传参类型为引用数据类型,方法内交换基本数据类型(传参对象的属性)生效
- 传参类型为引用数据类型,方法内交换引用数据类型(传参对象的属性的地址值)生效
总结:
方法内交换,对于传参进来的基本数据类型并不生效(情况1)
如果传参为引用数据类型,交换引用本身也不会生效(情况2)
如果传参为引用数据类型,交换引用的引用,会生效(情况3,4)
传参对象的属性,或者其地址值,是归属于传参对象的数据,如果获得它的地址值,操作它的数据,是能够生效的。
如果彻底理解栈和堆的概念,实质便只有两种情况,方法进栈,传参获得的数据永远是局部变量,局部变量无论怎么修改,出栈时都会被销毁,而如果通过传参的数据,对栈外(堆上的数据)进行操作,则方法出栈时,传参数据(局部变量)即使被销毁,在堆上做过的操作依然会被保留。