最近写代码时遇到了一个 “bug”,首先先让来看看是怎么样的;
public void merge(int A[], int m, int B[], int n){
Arrays.copyOf(A, m + n);
System.arraycopy(B,0, A, m, n);
}
我写了这么一个函数将 B合并到A中
int[] A = {23,34,2,3};
int[] B = {2,4,1,5,3};
merge(A,4,B,4);
log.info(Arrays.toString(A));
大家觉得上述代码可以运行成功么?
consle: [23, 34, 2, 3]
答案是不行,有人会说了,是你函数写错了、是你电脑的问题。但其实都不是,问题在于 Arrays.copyOf 是深拷贝,它返回的是一个新的数组。
此时 merge 方法中 的 A又重走了一遍 赋值操作,引用指向了新的 数组;
因此 此时 主函数 中的 A 没有任何改变
JAVA 方法传递参数
要探讨这个问题,首先我们要靠清楚一个概念,常见参数类型;
常见参数类型大致分为两种:基本类型 和 引用类型
1. 基本类型:
boolean, char, short, int, long, float, double, byte
这些类型声明的对象,当未使用 static 关键字时, 都是存储在 虚拟机栈的栈帧中的。
当方法A 调用 方法B 对应的就是 入栈
当方法B 执行完毕返回 对应的就是 出栈
从图中我们可以看出,A()和 B()“独立” 或者说 “局部”
的确如此,除了B()进栈时传递的参数, 和B()出栈时的返回值(return)。他们的确没有过多交互;
2. 引用类型:
A a = new A();
B b = a;
此时 a 指向的是 堆中 被实例化的对象 aObject, 现在的 a 其实是一个引用。
而当 b = a时,此时的 b 也指向的也是 堆中 被实例化的对象 aObject;
当此时 你利用 引用 a 修改 对象 aObject, 引用 b 指向的 对象也会跟着改变 。
因为它们指向的是同一个对象
这就是 浅拷贝 的原理
浅拷贝:拷贝的是对象的引用,会受被拷贝对象的影响
那么刚才讲的 和 方法传递有什么关系勒?
好了,不买关子,直接解答:
Java 方法参数传递 就相当于 赋值操作;
1. 传入基本类型 参数;
基本类型
void A(){
int i = 2;
B(i);
}
void B(int i){
i = i + 1;
}
// int i = 2;
当 传入参数i,此时B方法中相当于执行了 int i = 2 的操作;
因为栈帧 与 栈帧之间是 “独立”的,因此此刻 方法 A 和 方法 B中各自存在一个 i 变量。
因此 方法A中的 i 不受影响。
2. 传入引用参数:
引用类型
void A(){
User u = new User(" A ");
B(u);
}
void B(User user){}
// User user = u == new User(" A ");
此时 u 和 user 都指向堆中 同一个实例对象
因此在方法 B 中向 对象作操作,方法A 中的引用也会受到影响
文章开头的bug
引用类型
void A(){
User u = new User(" A ");
B(u);
}
void B(User user){
user = new User(" B ");
}
那么此时在 方法B中操作对象, 还会是 方法A中的引用 u 受影响么?
答案是不会;
其实此时方法B 执行了两个操作:
User user = u = new User(" A “);
user = new User(” B ");
很显然,此时的user 已经指向了 堆中的新对象了
此时 你在 B()中,这么改变对象,A()中都不会受影响
而 Arrays.copyof() 底层是深拷贝 返回的就是一个新的数组
深拷贝:拷贝的是对象的值,不受被拷贝对象的影响
总结
方法传参:
基本类型传递:值传递,深拷贝
引用类型传递:引用传递,浅拷贝
引用类型传递时,注意查看 引用是否在本对象中重新赋值,若出现赋值情况,调用者中引用 与 被调用者中引用 断开连接。
好了,今天就说到这里,喜欢笔者的可以点个赞,加个关注。
我将持续更新