【2021-07-09】请谈一下值传递与引用传递?Java 中只有值传递么?
请移步至:
每日一题 查看更多的题目 ~
请不要忽视这个问题,它也许没有你想的那么简单。绝大部分初学者很难搞懂究竟什么是值传递,什么是引用传递。
很多博客中,作者不仅没有解释清楚“值传递”与“引用传递”,还混淆了很多错误的引导。
所以,在回答这个问题之前,我们先进行一次排雷检查,看一看你是否对值传递与引用传递有着错误的理解:
- 【观点1】Java 中既有值传递也有引用传递
- 【观点2】Java 中只有值传递,因为引用的本质就是指向堆区的一个地址,也是一个值。
如果你的观点符合上述两种观点的其中一种,那么你多半没有理解值传递和引用传递的概念。话不多说,接下来我来详细说明究竟什么是值传递,什么是引用传递,以及 Java 中为什么只有值传递。
答:
值传递(Pass by value)与引用传递(Pass by reference)属于函数调用时,参数的求值策略(Evaluation Strategy)。求值策略的关注点在于,求值的时间以及传值方式:
求值策略 | 求值时间 | 传值方式 |
---|---|---|
Pass by value | 函数调用前 | 原值的副本 |
Pass by reference | 函数调用前 | 原值(原始对象) |
所以,区别值传递与引用传递的实质并不是传递的类型是值还是引用,而是传值方式,传递的是原值还是原值的副本。
如果传递的是原值(原对象),就是引用传递;如果传递的是一个副本(拷贝),就是值传递。再次强调一遍,值传递和引用传递的区别在于传值方式,和你传递的类型是值还是引用没有一毛钱关系!
Java 语言之所以只有值传递,是因为:传递的类型无论是值类型还是引用类型,Java 都会在调用栈上创建一个副本,不同的是,对于值类型而言,这个副本就是整个原始值的复制;而对于引用类型而言,由于引用类型的实例存储在堆中,在栈上只有它的一个引用,指向堆的实例,其副本也只是这个引用的复制,而不是整个原始对象的复制。
我们通过两个程序来理解下:
程序一:
public class Test {
public static void setNum1(int num){
num = 1;
}
public static void main(String[] args) {
int a = 2;
setNum1(a);
System.out.println(a);
}
}
程序二:
public class Test2 {
public static void setArr1(int[] arr){
Arrays.fill(arr,1);
}
public static void main(String[] args) {
int[] arr = {1,2,3,4,5};
setArr1(arr);
System.out.println(Arrays.toString(arr));
}
}
程序一输出的结果为:2
程序二输出的结果为:[1,1,1,1,1]
程序一中,Java 会将原值复制一份放在栈区,并将这个拷贝传递到方法参数中,方法里面仅仅是对这个拷贝进行了修改,并没有影响到原值,所以程序一的输出结果为 2。
程序二中,Java 会将引用的地址复制一份放在栈区,复制的拷贝和原始引用都指向堆区的同一个对象。方法通过拷贝地址找到堆区的实例,对堆区的实例进行修改,而此时,原始引用仍然指向着堆区的实例,程序二的输出结果为:[1,1,1,1,1]