专栏名称: 武哥聊编程
这里有技术,有段子,有生活,也有资源,要不然怎么叫 “私房菜” 呢?
目录
相关文章推荐
湖北经视  ·  张兰、汪小菲抖音账号,无限期封禁! ·  昨天  
湖北经视  ·  大S代言广告被撤下,品牌回应 ·  昨天  
新疆生态环境  ·  关于解除区域重污染天气黄色预警的公告 ·  2 天前  
新疆生态环境  ·  关于解除区域重污染天气黄色预警的公告 ·  2 天前  
湖北经视  ·  狂飙!暴涨!涨疯了! ·  3 天前  
51好读  ›  专栏  ›  武哥聊编程

为什么大家都说Java中只有值传递?

武哥聊编程  · 公众号  ·  · 2020-03-06 08:45

正文

最近跟Java中的值传递和引用传递杠上了,一度怀疑人生。 查了很多资料,加上自己的理解,终于搞清楚了,什么是值传递和引用传递。 也搞明白了,为什么大家都说Java只有值传递,没有引用传递。 原来,我一直以来的认知都是错误的。

首先,需要了解一些概念性的东西。

形参与实参:

形参,是指在定义函数时使用的参数,目的是用于接收调用该函数时传入的参数。 简单理解,就是所有函数(即方法)的参数都是形参。

实参,是指调用函数时,传递给函数的参数。

public static void main(String[] args) {
int num = 3;
printVal(num); //这里num是实参
}

private static void printVal(int num) {
num = 5; //这里num就是形参
}

值传递和引用传递

值传递: 是指在调用函数时,将实际参数复制一份传递给函数,这样在函数中修改参数时,不会影响到实际参数。 其实,就是在说值传递时,只会改变形参,不会改变实参。

引用传递: 是指在调用函数时,将实际参数的地址传递给函数,这样在函数中对参数的修改,将影响到实际参数。

这里,需要特别强调的是,千万不要以为传递的参数是值就是值传递,传递的是引用就是引用传递。 也不要以为传递的参数是基本数据类型就是值传递,传递的是对象就是引用传递。 这是大错特错的。 以前的我,一直都是这样认为的,现在想来真是太天真了。 判断是值传递还是引用传递的标准,和传递参数的类型是没有一毛钱关系的。以下举例说明。

下面三种情况,基本上可以涵盖所有情况的参数类型。

当传递的参数是基本数据类型时:

public class TestNum {
public static void main(String[] args) {
int num = 3;
System.out.println("修改前的num值:"+num);
changeValue(num);
System.out.println("修改后的num值:"+num);
}

private static void changeValue(int num) {
num = 5;
System.out.println("形参num值:"+num);
}
}

打印结果:

修改前的num:3
形参num:5
修改后的num:3

可以发现,传递基本数据类型时,在函数中修改的仅仅是形参,对实参的值的没有影响。

需要明白一点,值传递不是简单的把实参传递给形参,而是,实参建立了一个副本,然后把副本传递给了形参。 下面用图来说明一下参数传递的过程:

图中num是实参,然后创建了一个副本temp,把它传递个形参value,修改value值对实参num没有任何影响。

传递类型是引用类型时:

public class User {
private int age;
private String name;
publicint getAge() {
return age;
}

publicvoid setAge(int age) {
this.age = age;
}

public String getName() {
return name;
}

publicvoid setName(String name) {
this.name = name;
}

public User(int age, String name) {
this.age = age;
this.name = name;
}

public User() {
}

@Override
public String toString() {
return "User{" +
"age=" + age +
", name='" + name + '\'' +
'}';
}
}
public class TestUser {
public static void main(String[] args) {
User user = new User(18, "zhangsan");
System.out.println("修改对象前:"+user);
changeUser(user);
System.out.println("修改对象后:"+user);
}

private static void changeUser(User user) {
user.setAge(20);
user.setName("lisi");
}
}

打印结果:

修改对象前:User{age=18, name='zhangsan'}
修改对象后:User{age=20, name='lisi'}

可以发现,传过去的user对象,属性值被改变了。 由于,user对象存放在堆里边,其引用存放在栈里边,其参数传递图如下:

user是对象的引用,为实参,然后创建一个副本temp,把它传递给形参user1。 但是,他们实际操作的都是堆内存中的同一个User对象。 因此,对象内容的修改也会体现到实参user上。

传递类型是String类型(Integer等基本类型的包装类等同)

public class TestStr {
public static void main(String[] args) {
String str = new String("zhangsan");
System.out.println("字符串修改前:"+str);
changeStr(str);
System.out.println("字符串修改后:"+str);
}

private static void changeStr(String str) {
str = "lisi";
}
}

打印结果:

字符串修改前:zhangsan
字符串修改后:zhangsan

咦,看到这是不是感觉有点困惑。 按照第二种情况,传递参数是引用类型时,不是可以修改对象内容吗,String也是引用类型,为什么在这又不变了呢?

再次强调一下,传递参数是引用类型,并不代表就是引用传递,其实它还是值传递。 此时的 lisi 和上边的 zhangsan 根本不是同一个对象。 画图理解下:

图中,str是对象 zhangsan 的引用,为实参,然后创建了一个副本temp,把它传递给了形参str1。 此时,创建了一个新的对象 lisi ,形参str1指向这个对象,但是原来的实参str还是指向zhangsan。 因此,形参内容的修改并不会影响到实参内容。 所以,两次打印结果都是zhangsan。

第三种情况和第二种情况虽然传递的都是引用类型变量,但是处理方式却不一样。 第三种情况是创建了一个新的对象,然后把形参指向新对象,而第二种情况并没有创建新对象,操作的还是同一个对象。 如果把上边changeUser方法稍作改变,你就会理解:

private static void changeUser(User user) {
//添加一行代码,创建新的User对象
user = new User();
user.setAge(20);
user.setName("lisi");
}

运行以上代码,你就会惊奇的发现,最终打印修改前和修改后的内容是一模一样的。 这种情况,就等同于第三种情况。 因为,这里的形参和实参引用所指向的对象是不同的对象。 因此,修改形参对象内容并不会影响实参内容。

修改对象前:User{age=18, name='zhangsan'}
修改对象后:User{age=18, name='zhangsan'}

总结:

从以上三个例子中,我们就能理解了,为什么Java中只有值传递,并没有引用传递。 值传递,不论传递的参数类型是值类型还是引用类型,都会在调用栈上创建一个形参的副本。







请到「今天看啥」查看全文