JAVA值传递和引用传递的争论
今天一起秋招的小伙伴说面试官问了JAVA值传递和引用传递的问题,我不假思索的说是引用传递,结果引来一系列的讨论。但是以下几点是得到了共同的认识的:
- 如果是值传递,那么函数调用过程中对目标的改变不会导致原来目标发生变化,类似于c++里面的
swap(int a, int b)
函数,在运行函数过程中,内部的a,b
实际上是外部a,b
的一个拷贝,改变不会传递到外部。 - 如果是引用传递,在函数内部的改变会影响外部的值,似于c++里面的
swap(int &a, int &b)
,在运行函数过程中会改变外部的值。
实际上c++还有一种传递是指针传递,实际上是指针变量的值传递,这个不谈。那么JAVA里面的变量到底是值传递还是引用传递呢?我们分以下两种情况讨论:
1、基本类型变量的函数调用,这里面用int
和float
作为主要的类型,代码如下:
package com.lixiande.leetcode;
public class test {
public static void vallueCrossTest(int age , float weight){
System.out.println("函数内的age 地址" + System.identityHashCode(age));
System.out.println("函数内的weight 地址" + System.identityHashCode(weight));
age = 33;
weight = 89.5f;
System.out.println("函数内改变的age 地址" + System.identityHashCode(age));
System.out.println("函数内改变的weight 地址" + System.identityHashCode(weight));
}
public static void main(String[] args) {
int age = 10;
float weight = 10.01f;
System.out.println("函数外的age 地址" + System.identityHashCode(age));
System.out.println("函数外的weight 地址" + System.identityHashCode(weight));
vallueCrossTest(age,weight);
System.out.println("函数外改变的age 地址" + System.identityHashCode(age));
System.out.println("函数外改变的weight 地址" + System.identityHashCode(weight));
}
}
在函数调用前后分别获取对应参数的地址值(这里用System.identityHashCode
代替),结果如下:
函数外的age 地址728890494
函数外的weight 地址1558600329
函数内的age 地址728890494
函数内的weight 地址636718812
函数内改变的age 地址445051633
函数内改变的weight 地址1051754451
函数外改变的age 地址728890494
函数外改变的weight 地址1349277854
可以看到函数传递过程中的地址是不变的,但是对于基本类型int
改变了他的值会导致指向的地址发生改变,这个是因为常量池的原因,常量池会维护1-127
的int
值。而float类型对象则是发生了多次改变,分别是从1558600329->636718812->1051754451->1349277854,float
没有常量池,会在栈上找有没有相等的字面量,没有就会在栈上新开辟,所以地址发生了变化。
2、类对象的函数调用
这里面使用一个person
类作为测试类,同样使用System.identityHashCode
作为地址判断:
package com.lixiande.leetcode;
class person{
private String name;
public person(String name){
System.out.println("now i am init a new person");
this.name = name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "person{" +
"name='" + name + '\'' +
'}';
}
}
public class test {
public static void personCrossTest(person p ){
System.out.println("函数内的p 地址" + System.identityHashCode(p));
p.setName("lixiang");
System.out.println("函数内改变的p 地址" + System.identityHashCode(p));
}
public static void main(String[] args) {
person p = new person("松哥哥");
System.out.println(p.toString());
System.out.println("函数外的p 地址" + System.identityHashCode(p));
personCrossTest(p);
System.out.println("函数外的改变后p 地址" + System.identityHashCode(p));
System.out.println(p.toString());
}
}
结果如下:
now i am init a new person
person{name='松哥哥'}
函数外的p 地址1775282465
函数内的p 地址1775282465
函数内改变的p 地址1775282465
函数外的改变后p 地址1775282465
person{name='lixiang'}
首先,函数内外地址发生改变了吗?没有,也就是内部的person
指向的和外部person
指向的对象没有发生改变。这里面加入修改函数内person
指向的方式进行操作,将函数修改为
public static void personCrossTest(person p ){
System.out.println("函数内的p 地址" + System.identityHashCode(p));
p = new person("lixiang");
System.out.println("函数内改变的p 地址" + System.identityHashCode(p));
}
结果为:
now i am init a new person
person{name='松哥哥'}
函数外的p 地址1775282465
函数内的p 地址1775282465
now i am init a new person
函数内改变的p 地址1147985808
函数外的改变后p 地址1775282465
person{name='松哥哥'}
也就是我重新修改了p
的指向不会改变外部的p
的指向,从这个角度上来说确实是内部p的指向的改变没有影响外部的p
的指向。这个是不是很像c++里面指针传递的过程呢,改变了函数内改变了指针的指向,但是函数外的指针指向不会发生变化。
原因解释:
每一个函数调用栈都会存储在函数调用过程中产生的数据(比如没有常量池的基本类型,或者堆里的引用,或者常量池里面的引用),那么每次调用函数过程会开辟新的函数调用栈,这个栈地址和原来函数的调用地址必然是不同的。以上面的代码为例,main的栈里面的p自然和personCrossTest
里面的p
所在的位置自然不同(因为栈都不同),但是里面的地址一开始是相同的,指向堆内同一个person对象,在personCrossTest
函数内部改变p
的指向相当于改变了personCrossTest
的p
的值,使personCrossTest
栈的p指向了一个新的person
对象,而在main
的栈里面的p
没有改变值自然是没有改变对象。
总结:
1、传递的数据是什么?我个人认为是调用栈给被调用栈传递了一个对象的地址,如果对象为基本类型,则可能是要自己开辟。
2、从堆对象的角度来说传递的是堆内对象的一个地址(或者说引用)。
3、从栈的变量的角度来说传递的是调用栈的地址的复制,也就是地址的值传递。
以上是我个人的理解,希望大家和我纠正和讨论。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。