问题引出
两个Integer的引用对象传给一个swap方法在方法内部进行交换(返回后,两个引用的值是否会发生变化),swap交换函数如何编写使得两个值交换?
public static void main(String[] args) {
Integer a=1,b=2;
System.out.println("before:a="+a+",b:="+b);
swap(a,b);
System.out.println("after:a="+a+",b:="+b);
}
public static void swap(Integer i1,Integer i2){
//TODO
}
问题解决
首先,答案是不是简单地交换两个值?
答:错误!swap传递的基本类型数据,副本式的地址,不会影响到原始值
public static void swap(Integer i1,Integer i2){
//TODO
Integer tmp = i1;
i1 = i2;
i2 = tmp;
}
所以,我们看一下,Integer的值存在哪里了?
public final class Integer extends Number implements Comparable<Integer> {
private final int value;
public Integer(int value) {
this.value = value;
}
}
打开源码发现,存在私有的final常量里!所以,想要改变Integer的值,只能通过反射改变其内存地址。于是
public static void swap(Integer i1,Integer i2) throws NoSuchFieldException, IllegalAccessException{
//TODO
Field field = Integer.class.getDeclaredField("value");
//暴力访问私有成员变量
field.setAccessible(true);
int tmp = i1.intValue();
field.set(i1,i2.intValue());
field.set(i2,tmp);
}
我们运行一下:
before:a=1,b:=2
after:a=2,b:=2
发现b还是没有改变!用javap看一下字节码被编译的过程
命令:javap -v ‘字节码文件名称’
我们看main方法jvm执行指令,发现是Integer.valueOf(i)进行赋值:
相当于我们在Integer a = 1的时候,Integer a = Integer.valueOf(1);
所以我们看一下Integer的valueOf
public static Integer valueOf(String s, int radix) throws NumberFormatException {
return Integer.valueOf(parseInt(s,radix));
}
点进去,看一下实现过程,真相大白!
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
原因是Integer存在缓存,范围值-128到127,相当于就是一个字节byte的取值范围。
举个栗子:
Integer a=1,b=1;
System.out.println(a==b);
通过valueOf方法不难分析,答案就是true
好,回归正题,我们如何避免这个缓存机制呢?
public static void swap(Integer i1,Integer i2) throws NoSuchFieldException, IllegalAccessException{
//TODO
Field field = Integer.class.getDeclaredField("value");
//暴力访问私有成员变量
field.setAccessible(true);
//int tmp = i1.intValue();
Integer tmp = new Integer(i1.intValue());
field.set(i1,i2.intValue());
field.set(i2,tmp);
}
根据valueOf源码:直接new出来Integer,不走判断逻辑,问题得以解决!
输出:
before:a=1,b:=2
after:a=2,b:=1
知识点总结
- 值传递和引用传递
Java中的数据类型分为两种为基本类型和引用类型。
函数传递引用类型:函数接收的是原始值的地址,所以原始值会变。
函数传递基本类型:函数接收的是原始值的副本,所以原始值不变。
-
自动装箱和拆箱
-
Integer -128到127之间的缓存
-
反射:通过反射去修改private final变量
题外
通过这个面试题,想到了java基础篇之基本数据类型的取值范围如何获得?以及位运算符的相关操作,后续更新!