Java子函数是否会直接改变主函数中的值,Integer等包装类为什么搞特殊?
在读《剑指Offer》时,看到一个字符串操作的讲解,引发一个思考:
Java语言中,子函数是否会直接改变主函数中的值?
答:分情况。
两种情况:
- 基本数据类型:
简单的值传递,不会改变主函数中的值。 - 引用类型(对象的引用):
引用的值传递(相当于指针的地址传递)可以改变对象属性的值。
数组也是一种引用。
实际上是引用的值传递(两个引用指向同一个对象,可以理解为对象的地址传递)
这个时候只能改变引用所指向对象的属性,而不是对对象本身的修改
(不能把一只狗变成猫,只能修改狗的名字)。
回顾
在大一学习C语言的时候,大家在学习函数这一章节的时候,所有老师都会特意强调:
函数调用时是值的传递,子函数形参的改变,并不会影响主函数的实参值。
很典型的例子就是两个数做交换,这里就不再列举了。
如果想在子函数里修改实参的值,在C语言中有三种方法:
- 用数组存储待修改参数
- 利用指针
- 用返回值返回待修改的参数
第一种方法是利用C语言中地址的传递(指针),数组名本身就是个不可修改的指针,指向数组中第一个元素的地址。
因此,前两种方法本质上是一样的。
在C++中,也可用引用类型操作符号&来绑定实参和形参。
问题
那么问题来了,在Java中,并不能直接操控指针,也没有&这种引用类型,怎么办呢?
解决方案
Java虽然不能显式操作指针,但是可以操作对象的引用。
利用对象的引用,可以直接修改对象的属性(如果访问权限允许的话),如:
public class Test {
public static void main(String[] args){
Person person = new Person("ZhanSan",22);
fun(person);
System.out.println("主函数:"+person);
}
private static void fun(Person p) {
p.age++;
System.out.println("子函数:"+p);
}
}
class Person{
String name;
int age;
public Person(String name,int age){
this.name = name;
this.age = age;
}
@Override
public String toString() {
return this.name+" "+this.age;
}
}
输出:
子函数:ZhanSan 23
主函数:ZhanSan 23
这样是没问题的。
虽然主函数的person
和子函数的p
都是指向的同一个对象,但是他俩是完全不同的两个引用(可以理解为C语言中的两个不同的指针存着同一个地址,从而指向同一个变量)
但是如果我们想直接传输一个基本数据类型的值进行修改怎么办?
先直接传传看看:
public static void main(String[] args){
int i1 = 1;
fun(i1);
System.out.println("主函数:"+i1);
}
public static void fun(int i2){
i2++;
System.out.println("子函数:"+i2);
}
输出:
子函数:2
主函数:1
显然不行!
那么,上面提到,可以中对象来操作,而Java语言又对基本数据类型提供了包装类,那么是否可以用包装类来试试呢?
public static void main(String[] args){
Integer i1 = new Integer(1);
fun(i1);
System.out.println("主函数:"+i1);
}
public static void fun(Integer i2){
i2++;
System.out.println("子函数:"+i2);
}
输出:
子函数:2
主函数:1
???
为什么还是不行,前面不是说用对象就可以传的嘛!
开头就提到,可以对对象的属性进行操作,但是并不支持对对象本身进行操作。
我打断点调试发现,在进行i++
操作之后,对象引用的值就发生了改变,换句话说,创建了一个新对象
我们来分析一下,上述代码中:
i2++;//实际上进行了自动拆装箱操作,这是Java5的特性
//实际上,它等同于下面三行
int i = i2.intValue();//拆箱
i++;
i2= Integer.valueOf(i);//装箱
而在装箱的过程中,发生了如下操作(看源码):
// Integer源码
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
简单解释一下,如果-128<i<127,直接从Integer自带缓存中拿一个Integer对象出来(效率会更高)
否则,直接返回一个新对象。
因此,无论如何,都会返回一个新对象。
切记,我们通过对象的引用,能改变的,只是对象的属性,而不是对象本身。
因此,由于Integer本身就是个对象,所以是没法改变的。
本质上,值的改变,只是自动拆箱,变成基本数据类型了之后,才进行了运算,
运算完成后,再自动装箱,包装成一个对象。
结论
Java中,值的传递分两种:
- 基本数据类型:
简单的值传递,不会改变主函数中的值。 - 引用类型(对象的引用):
引用的值传递(相当于指针的地址传递)可以改变对象属性的值。
数组也是一种引用。
包装类看似特殊,实则不是,只是包装类没有属性供我们修改罢了(这个属性被私有化了,且被final修饰,不可修改)
// Integer源码
/**
* The value of the {@code Integer}.
*
* @serial
*/
private final int value;