基本数据类型的包装类java.lang.Integer是我们频繁使用的一个系统类,那么通过一个示例反应出的几个问题来深入理解一下此类的源码。
需求:实现Integer类型的两个数值交换。
1 packagecn.integer;2
3 public classDemo {4 public static voidmain(String[] args) {5 Integer a = 1;6 Integer b = 2;7 System.out.println("bofore swap a:"+a+",b:"+b);8 //交换a和b的值
9 swap(a,b);10 System.out.println("after swap a:"+a+",b:"+b);11 }12 /**
13 * 数据交换14 *@parama15 *@paramb16 */
17 private static voidswap(Integer a, Integer b) {18 /**
19 * 如何实现呢?尝试几种方法20 */
21 /*01.方式一(无法实现)22 a = a^b;23 b = a^b;24 a = a^b;*/
25
26 /*02.方式二(无法实现)27 int tmp = a;28 a = b;29 b = tmp;*/
30
31 /**
32 * 以上两种方式是因为java的值传递特性故无法实现数据交换。33 */
34 }35 }
Java值传递的示意图如下:
当调用swap(..)方法时,在堆中会创建这两个值得副本,形参num1和num2指向副本的数据。原ab指向的数据不会改变。
那么如何通过修改swap()方法实现呢?
观察java.lang.Integer源码:
public final class Integer extends Number implements Comparable{
...
}
可以发现 Integer和String一样,是final修饰的,这是因为jdk将这些系统类封装起来不希望被破坏。
继续看Integer类:
/*** The value of the {@codeInteger}.
*
*@serial
*/
private final intvalue;/*** Constructs a newly allocated {@codeInteger} object that
* represents the specified {@codeint} value.
*
*@paramvalue the value to be represented by the
* {@codeInteger} object.*/
public Integer(intvalue) {this.value =value;
}
这里的value也是使用final修饰的,那么如果想修改它的值,可以使用反射的方式获取value字段并进行修改。
修改swap()方法中代码:
1 public classDemo {2 public static voidmain(String[] args) {3 Integer a = 1;4 Integer b = 2;5 System.out.println("bofore swap a:"+a+",b:"+b);6 //交换a和b的值
7 swap(a,b);8 System.out.println("after swap a:"+a+",b:"+b);9 }10 /**
11 * 数据交换12 *@parama13 *@paramb14 */
15 private static voidswap(Integer a, Integer b) {16 try{17 //获取Integer类中私有字段value
18 Field field = Integer.class.getDeclaredField("value");19 /**
20 * 开启访问权限21 * public final class Field extends AccessibleObject implements Member {}22 * 跟进AccessibleObject23 * public void setAccessible(boolean flag) throws SecurityException {24 SecurityManager sm = System.getSecurityManager();25 if (sm != null) sm.checkPermission(ACCESS_PERMISSION);26 setAccessible0(this, flag);27 }28
29 private static void setAccessible0(AccessibleObject obj, boolean flag)30 throws SecurityException31 {32 if (obj instanceof Constructor && flag == true) {33 Constructor> c = (Constructor>)obj;34 if (c.getDeclaringClass() == Class.class) {35 throw new SecurityException("Cannot make a java.lang.Class" +36 " constructor accessible");37 }38 }39 obj.override = flag;40 }41 */
42 field.setAccessible(true);43 //临时变量
44 int tmp =a.intValue();45 //数据交换
46 field.set(a, b);47 field.set(b, tmp);48 } catch(Exception e) {49 e.printStackTrace();50 }51 }52 }
运行结果:
诶,我去。怎么只改变了一个值?咋成功了一半呢?
01.首先,Integer a = 1; 这里会自动装箱,将 int类型的1 转换成 Integer类型。
通过valueOf方法进行装箱
1 public static Integer valueOf(inti) {2 if (i >= IntegerCache.low && i <=IntegerCache.high)3 return IntegerCache.cache[i + (-IntegerCache.low)];4 return newInteger(i);5 }
这个方法非常重要,如果传入的参数在 -128-127之间,会返回一个缓存中的数据。否则就 new出一个Integer对象!
Integer.IntegerCache是Integer中的内部类:
1 private static classIntegerCache {2 static final int low = -128;3 static final inthigh;4 static finalInteger cache[];5
6 static{7 //high value may be configured by property
8 int h = 127;9 String integerCacheHighPropValue =
10 sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");11 if (integerCacheHighPropValue != null) {12 try{13 int i =parseInt(integerCacheHighPropValue);14 i = Math.max(i, 127);15 //Maximum array size is Integer.MAX_VALUE
16 h = Math.min(i, Integer.MAX_VALUE - (-low) -1);17 } catch( NumberFormatException nfe) {18 //If the property cannot be parsed into an int, ignore it.
19 }20 }21 high =h;22
23 cache = new Integer[(high - low) + 1];24 int j =low;25 for(int k = 0; k < cache.length; k++)26 cache[k] = new Integer(j++);27
28 //range [-128, 127] must be interned (JLS7 5.1.7)
29 assert IntegerCache.high >= 127;30 }31
32 privateIntegerCache() {}33 }
02.当我们获取数值1时,
return IntegerCache.cache[i + (-IntegerCache.low)]
计算出IntegerCache.cache[129]的值(也就是1)返回。
cache这个数组存放着 -128-127这些数据。故index=129就是返回1。
03. 执行这行时 field.set(a, b);将传入的2复制给a,此时a为2.
注意:这里通过set方法改变的的是缓存cache数组中的数据!04. 当执行到 field.set(b, tmp);
tmp为1,而set方法的参数类型是Object,可以传入int类型的tmp
public voidset(Object obj, Object value)throwsIllegalArgumentException, IllegalAccessException
{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class> caller =Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
getFieldAccessor(obj).set(obj, value);
}
所以这个tmp=1是从Integer的缓存数组中取得,因上一行操作已经将cache中index为129位置得原数值1改变为了2,故在cache中获取的是2! 那么把2赋值给了b自然是2!(b对应的下标[130],a对应的下标[129])
我们发现,问题的关键在于这个缓存cache!
如果说可以避开走缓存这一步,我们就能实现数据交换。除了传入 -128-127之外的数据,我们还可以:
001.将tmp转换成Integer对象后在传入Field的set方法:
private static voidswap(Integer a, Integer b) {try{//获取Integer类中私有字段value
Field field = Integer.class.getDeclaredField("value");
field.setAccessible(true);//临时变量
int tmp =a.intValue();//数据交换
field.set(a, b);
field.set(b,newInteger(tmp));
}catch(Exception e) {
e.printStackTrace();
}
}
运行结果:
002. 使用 Field类的 setInt方法
public void setInt(Object obj, inti)throwsIllegalArgumentException, IllegalAccessException
{if (!override) {if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
Class> caller =Reflection.getCallerClass();
checkAccess(caller, clazz, obj, modifiers);
}
}
getFieldAccessor(obj).setInt(obj, i);
}
修改代码 field.setInt(b,tmp); 也可以实现。
003. 非要实现打印效果的话,使用非常规手短,直接在swap中打印结果...
private static voidswap(Integer a, Integer b) {
System.out.println("after swap a:2,b:1");
System.exit(0);
}
这个示例中涉及到技能点: