先给出一道面试题:
在main中定义两个Integer变量,通过swap方法交换值
碰到这道题,大家的第一反应就是这样写
public static void swap(Integer a1, Integer b1) {
int temp = a1;
a1 = b1;
b1 = temp;
}
这样写肯定是错误的。为什么呢,Integer不是引用类型吗, 我传参传的是引用类型,不就是地址吗,用地址去操作,为什么不能交换,通过内存模型图来看看为什么
当完成交换后,只是a1和b1交换了,对a和b没有影响
这就要引出Java中的传递方式,Java中有两种参数传递的方式,一种是值传递,一种是引用传递,但是严格意义上来讲,引用传递不算真正的引用传递,是值传递的特殊情况,传递的是引用的值。
我们在一个函数中去修改一个对象的数据时,引用传递会起到作用,因为函数拷贝了一份引用,两个引用同时指向了堆上的同一个对象,所以函数中对引用所指的对象进行操作是完全有效的。
所以针对这个特点,我们可以想到解决这个问题的一个方法
修改对象的内部数据
因为我们在函数中通过引用所做的操作在函数结束后是有效的,所以我们可以修改对象中的数据,先举个简单的例子
我们有一个Person类,通过传递Person的两个实例来交换两个Person实例
public class Test04 {
public static void main(String[] args) {
Person person1 = new Person("张三");
Person person2 = new Person("李四");
swap(person1,person2);
System.out.println(person1);
System.out.println(person2);
}
public static void swap(Person person1,Person person2) {
String temp = person1.getName();
person1.setName(person2.getName());
person2.setName(temp);
}
}
class Person {
private String name;
public Person(String name) {
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\'' +
'}';
}
}
可以看到结果是正确的。
通过数组辅助
还有一种方法是通过数组辅助的方式来进行交换,我们先通过内存图来了解
然后我们交换数组的0号和1号下标
这样就完成了交换
还是上面的案例,这下我们需要修改一下swap函数
public static void main(String[] args) {
Person person1 = new Person("张三");
Person person2 = new Person("李四");
Person[] persons = {person1,person2};
swap(persons);
System.out.println(persons[0]);
System.out.println(persons[1]);
}
public static void swap(Person[] persons) {
Person temp = persons[0];
persons[0] = persons[1];
persons[1] = temp;
}
结果一样是正确的,但是这个方法需要辅助数组,如果题目规定了函数头,并且没有提供辅助数组,这种方法就不可行。
总结了交换对象的两种方式,我们来看这个面试题
题目并没有规定函数头,所以我们可以自己写一个辅助数组,这种方法很简单,不做介绍。
主要来看一下如何应用第一种方法完成交换,也就是去改变Integer内部的数据成员
通过对Integer源码的了解,Integer用来代替基本类型变量int的数据属性是
但是由于封装性,没有提供set方法,那么就需要用到反射
按照我们的思路,得到Class对象,获得私有属性,set方法修改实例的私有属性
public static void swap(Integer a,Integer b) {
Class<Integer> integerClass = Integer.class;
Integer temp = a;
try {
Field value = integerClass.getDeclaredField("value");
value.setAccessible(true);
value.set(a,b);
value.set(b,temp);
} catch (Exception e) {
e.printStackTrace();
}
}
我们看看结果是什么
这是为什么呢?jdk5以后提供了包装类的自动装箱,底层调用了valueof方法,
Integer底层维护了一个在-128到127之间的数组,如果i的范围在该范围内,会通过i进行一系列数组下标的运算,然后找到数组中对应的对象,如果不在该范围内,就直接去new。
问题出在哪?当完成第一个set的时候,观察数组的情况
原本1的位置变成了2,所以这个时候a就变成了2,temp因为也是字面量赋值,跟着变成了2
这就是问题所在,temp是2,把2又给了b,所以结果是2,2。我们需要new一个temp对象出来,不从缓存数组中拿值,这样temp就不会受到缓存数组的影响。修改代码