Java是值传递还是引用传递
关于是值传递还是引用传递,我个人觉得没有必要纠结这个问题。当调用方法传参的时候,底层的逻辑是不变的,但是不同的人对于这两个词有不同的理解,我们只需要搞懂参数传递的原理即可。
1.基本数据类型和引用数据类型
想要弄懂今天的问题,首先需要知道基本数据类型和引用数据类型,直接上图:
基本数据类型直接在栈内存,存值
引用数据类型在栈内存存的是引用,引用指向堆内存里面的真正的值
2.执行方法传参到底发生了什么
2.1方法参数为基本数据类型
public class Test02 {
public static void change(int num){
num = 2;
}
public static void main(String[] args) {
int a = 1;
change(a);
System.out.println(a); //输出1
}
}
一段非常简单的代码,我先不说它到底输出了什么,我画一下整个过程的内存图:
main方法压栈:a变量赋值为1,调用change方法
change方法压栈:num已经将a所存的内容复制一遍给了自己
所以很明显a和num是两块内存,change方法让num变成2,和a没有任何关系,所以上面代码的输出是1
2.2方法参数是引用数据类型
public class Test02 {
public static void change(Cat cat){
cat = new Cat(10); //注意看这里!!!
}
public static void main(String[] args) {
Cat myCat = new Cat(5);
change(myCat);
System.out.println(myCat.getAge()); //输出是5!!!
}
}
class Cat{
private int age;
public Cat(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
和第一段代码思路相同,只是把change方法里面的int类型参数,改成了Cat类型,我画一下整个过程的内存图:
change方法中cat = new Cat(10)语句执行之前:
change方法中cat = new Cat(10)语句执行之后:(cat的内存地址不应该是0x123了,会发生改变,这里当时做图的时候疏忽了)
显然,change方法改变的是它内部的cat,没有影响到main方法里面的myCat,所以最后输出还是5
综上所述,无论参数是基本数据类型,还是引用数据类型。参数的来源者(a,myCat)和参数的接收者(num,cat)是两个不同的东西,占了两块不同的内存。参数来源者将自己存储的东西(可能是值,也可能是内存地址)复制一份传给了参数接收者。而方法(上面的change方法)内部操作的是参数接收者。
OK,读到这里可能会有小伙伴提问了,照你这么说那就是肯定不会有影响了,那下面这段代码怎么回事呢?为什么myCat受到影响了呢?
public class Test02 {
public static void change(Cat cat){
cat.setAge(10); //这里做了修改
}
public static void main(String[] args) {
Cat myCat = new Cat(5);
change(myCat);
System.out.println(myCat.getAge()); //输出变成了10!!!
}
}
class Cat{
private int age;
public Cat(int age) {
this.age = age;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
好的,我们直接上内存图:
change方法中cat.setAge(10)语句执行之前:(change执行之前内存图和上面是一样的)
change方法中cat.setAge(10)语句执行之后:(这里不一样了)
大家要仔细体会cat = new Cat(10)和 cat.setAge(10)两句代码的差别,结合上面的内存图。
cat = new Cat(10) 是直接改变了cat的值,让cat指向了堆内存中新的对象,它并没有对原来指向的对象做任何修改,而myCat还是指向原来的对象,自然不会受到任何影响。
cat.setAge(10)是通过cat这把钥匙进入到了堆内存中对象的内部,通过setAge方法改变了原来对象的属性值,cat和myCat依然指向堆中的同一个对象,但此时对象的属性已经被cat改变了,于是myCat自然收到了影响。
OK我知道肯定有的小伙伴又要问了,那String也是引用数据类型呀,为什么改变String的值,不会有影响呢?好的我们看下面这段代码:
public class Test02 {
public static void change(String str){
str = "world"; //这里做了修改
}
public static void main(String[] args) {
String myStr = "hello";
change(myStr);
System.out.println(myStr); //输出的是hello!!!
}
}
今天就给大家讲到底!!!
看内存图:
change方法执行str = "world"之前:(myStr和str指向了同一个hello)
change方法执行str = "world"之后:
大家需要搞明白str = "world"这句代码发生了什么。其实关于new String(“world”)和 = "world"的区别还是需要单独写一篇文章的,这里就先不详细说了,之后我会给大家分享一下。OK回归正题。
String类型的值是不可以修改的,这里说的修改,是针对内存来说的,一旦你指定了一个初始值str = “hello”,那堆中的这块内存就不可以发生改变了,不能再把这块内存里面的值改成"world",所以底层的操作是新开辟了一块"world"内存,然后让str指向新的对象。
所以change方法里面的代码 str = “world"其实是让str指向了新的对象,并不是对原来的对象进行了修改,而myStr依然指向原来的对象"hello”,所以输出的结果依然是"hello"。
综上所述,参数传递的本质就是将参数来源者的内容复制一份,丢给参数接收者。假如这个内容是引用,那么就相当于它们两者都拥有了打开堆中对象大门的钥匙,假如参数接收者把钥匙丢了换了一把新钥匙(比如new操作,创建了一个新对象,并让自己指向它),那么堆中原始对象根本没有受到任何影响,参数来源者访问原始对象的属性也不会有任何改变。但是假如参数接收者通过这把钥匙,打开了堆中对象的大门,并且对里面的东西做了修改(比如上面的cat.setAge操作),那么堆中的原始对象发生了改变,此时参数来源者再去访问堆中的对象属性,也就发生了改变。
OK到这里就结束了,笔芯~