关于 Java 值传递与引用传递的讨论有很多,先来了解一下值传递和引用传递。
值传递:是指在调用函数时将实际参数复制一份传递到函数中,这样在函数中如果对参数进行修改,将不会影响到实际参数。
引用传递:是指在调用函数时将实际参数的地址传递到函数中,那么在函数中对参数所进行的修改,将影响到实际参数。
概念知道了,接下来看问题:
public static void main(String[] args) {
int a = 0;
char[] c = {'a', 'b', 'c'};
change(a, c);
System.out.println("a:" + a);
System.out.println("c[0]:" + c[0]);
}
private static void change(int a, char[] c) {
a = 2;
c[0] = '0';
}
输出结果为:
a:0
c[0]:0
这种写法很容易让人产生误解,认为 change 方法里直接对 a 重新赋值,直接对 c[0]重新赋值,我们换个写法:
public static void main(String[] args) {
int a = 0;
char[] c = {'a', 'b', 'c'};
change(a, c);
System.out.println("a:" + a);
System.out.println("c[0]:" + c[0]);
}
/**
* 为避免混淆,换个参数名
*/
private static void change(int aa, char[] cc) {
aa = 2;
cc[0] = '0';
}
输出结果为:
a:0
c[0]:0
change 方法里对 aa 的修改并没有影响到 main 方法里 a 的值,所以第一个参数是值传递没有问题。但是对 cc[0]的修改却影响到了 main 方法里 c[0]的值,这是否说明第二个参数是引用传递呢?我们再来修改一下代码:
public static void main(String[] args) {
int a = 0;
char[] c = {'a', 'b', 'c'};
change(a, c);
System.out.println("a:" + a);
System.out.println("c[0]:" + c[0]);
}
/**
* 为避免混淆,换个参数名
*/
private static void change(int aa, char[] cc) {
aa = 2;
cc = null;
}
输出结果为:
a:0
c[0]:a
由此可知,对 cc 的修改其实并不会影响 main 方法里的 c。那么刚才为什么可以影响呢?因为调用 change 方法时,我们把 c 当做参数传了进去,而在 change 方法里,又有一个形参 cc,此时这两个引用同时指向同一个内存地址,也就是说,这时候不管用哪个引用去操作这块内存区域的数组,都会导致数组发生变化,但是请注意,这里说的是对数组进行操作,如果在 change 方法里把 cc 赋值为 null,这个就不是对数组进行操作了,而是把 cc 由指向数组变为指向 null,相当于有两个变量,一个是 c,一个是 cc,不管把 cc 赋值成什么,都与 c 无关。所以,这里并没有引用传递,也是值传递。
再来看一个进阶版:
public static void main(String[] args) {
String str = "abc";
char[] c = {'a', 'b', 'c'};
change(str, c);
System.out.println("str:" + str);
System.out.println("c[0]:" + c[0]);
}
/**
* 为避免混淆,换个参数名
*/
private static void change(String s, char[] cc) {
s = "xyz";
cc[0] = '0';
}
输出结果为:
str:abc
c[0]:0
两个参数都是引用数据类型,怎么会一个改变了,一个没有改变呢?有些解释是,string 是 final 修饰的,不能改变,所以虽然 string 是引用数据类型,但是它是个特例,是值传递,但是数组能改变,是引用传递。通过上面的分析,我们已经知道,不管是引用数据类型,还是基本数据类型,其参数传递都是值传递,但是引用数据类型是可以改变其属性的,那为什么 string 没有改变,难道真的是特例吗?
我们都知道,字符串其实也就相当于是复杂点的 char 数组,仔细观察会发现,change 方法里面对字符串 s 的操作其实是相当于改变其引用,并没有对字符串里的数组进行属性改变,而对数组 cc 的操作却是改变数组的属性,也就是说,这两个操作根本不在一个层面。要么就都改变引用,要么就都改变属性,操作保证一致,才能够说明问题。
修改代码,使 change 改变属性:
public static void main(String[] args) throws Exception {
String str = "abc";
char[] c = {'a', 'b', 'c'};
change(str, c);
System.out.println("str:" + str);
System.out.println("c[0]:" + c[0]);
}
/**
* 为避免混淆,换个参数名
*/
private static void change(String s, char[] cc) throws Exception {
Field valueFieldOfString = String.class.getDeclaredField("value");
valueFieldOfString.setAccessible(true);
char[] value = (char[]) valueFieldOfString.get(s);
value[0] = '0';
cc[0] = '0';
}
输出结果为:
str:0bc
c[0]:0
通过反射获取到 string 的私有 char 数组属性,并修改数组的第一个元素,说明 string 并不是不能修改,也不是什么特例。change 方法影响到了 main 方法里的原始变量,但是原因是我们上面分析的,只是因为两个引用都指向同一个内存地址而已,本质是值传递,并不是引用传递。
修改代码,使 change 改变引用:
public static void main(String[] args) {
String str = "abc";
char[] c = {'a', 'b', 'c'};
change(str, c);
System.out.println("str:" + str);
System.out.println("c[0]:" + c[0]);
}
/**
* 为避免混淆,换个参数名
*/
private static void change(String s, char[] cc) {
s = null;
cc = null;
}
输出结果为:
str:abc
c[0]:a
并没有影响 main 方法中原始变量,值传递。
通过上面的一系列分析,我们可以确定,Java 中不管是基本数据类型,还是引用数据类型,都只存在值传递,不存在引用传递,更不存在什么特例。
欢迎来到这里!
我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。
注册 关于