jdk之java.lang.Integer源码理解
基本数据类型的包装类java.lang.Integer是我们频繁使用的一个系统类,那么通过一个示例反应出的几个问题来深入理解一下此类的源码。
需求:实现Integer类型的两个数值交换。
1 package cn.integer; 2 3 public class Demo { 4 public static void main(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 * @param a 15 * @param b 16 */ 17 private static void swap(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> { ... }
可以发现 Integer和String一样,是final修饰的,这是因为jdk将这些系统类封装起来不希望被破坏。
继续看Integer类:
/** * The value of the {@code Integer}. * * @serial */ private final int value; /** * Constructs a newly allocated {@code Integer} object that * represents the specified {@code int} value. * * @param value the value to be represented by the * {@code Integer} object. */ public Integer(int value) { this.value = value; }
这里的value也是使用final修饰的,那么如果想修改它的值,可以使用反射的方式获取value字段并进行修改。
修改swap()方法中代码:
1 public class Demo { 2 public static void main(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 * @param a 13 * @param b 14 */ 15 private static void swap(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 * 跟进AccessibleObject 23 * 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 SecurityException 31 { 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(int i) { 2 if (i >= IntegerCache.low && i <= IntegerCache.high) 3 return IntegerCache.cache[i + (-IntegerCache.low)]; 4 return new Integer(i); 5 }
这个方法非常重要,如果传入的参数在 -128-127之间,会返回一个缓存中的数据。否则就 new出一个Integer对象!
Integer.IntegerCache是Integer中的内部类:
1 private static class IntegerCache { 2 static final int low = -128; 3 static final int high; 4 static final Integer 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 private IntegerCache() {} 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 void set(Object obj, Object value) throws IllegalArgumentException, 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 void swap(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, new Integer(tmp)); } catch (Exception e) { e.printStackTrace(); } }
运行结果:
002. 使用 Field类的 setInt方法
public void setInt(Object obj, int i) throws IllegalArgumentException, 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 void swap(Integer a, Integer b) { System.out.println("after swap a:2,b:1"); System.exit(0); }
这个示例中涉及到技能点: