Java涉及知识点
- 装箱和拆箱
- 反射及设置对象访问权限原理
- 函数参数的值传递和引用传递
- Integer原理及缓存机制
- 实现一个函数来交换参数的两个值
1. 装包和拆包
java提供的基本类型以及其对于的包装类型
以Integer为例子介绍装箱和拆箱:”
装箱存在的情况: int 赋值给Integer 即基本类型赋值给包装类型时
Integer i = 1; 等价于 Integer i = Integer.valueOf(1); java自动完成装箱操作
拆箱存在的情况: int与Integer比较 及基本类型和包装类型比较时
Integer i = new Integer(1);int j = 1;i == j //等价于 i.intValue() == j i对象会自动拆箱int类型和j进行比较
2. 反射及设置对象访问权限原理
JAVA反射机制是运行过程中,任意实体都可可以知道这个实体类的对象的所有方法和属性信息 对于任何对象都可以调用它的任意方法和属性,这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
这里我们反射的目标是修改某个类的private属性值。以Integer为例:
//获取Integer类对象的方式//Class clazz = Class.forName("java.lang.Integer");Class clazz = Integer.class;//获取Integer类中私有属性value的域 getDeclaredField获取类本身对应的所有访问//权限的属性 getField获取类以及父类所有的public属性Field field = clazz.getDeclaredField("value"); field.setAccessible(true); //设置private域访问权限Integer a = 1;field.set(a, 2);//将a 对象中的value属性设置为2
对象访问权限原理
反射访问对象的原理主要看Field类的setAccessible和set即可。一个设置域的可见性,一个设置属性值。
Field类声明:public final class Field extends AccessibleObject implements Member
//setAccessible源码 父类AccessibleObject的方法 public void setAccessible(boolean flag) throws SecurityException { SecurityManager sm = System.getSecurityManager(); if (sm != null) sm.checkPermission(ACCESS_PERMISSION); setAccessible0(this, flag);//前面是一安全管理器判断,这里才是真正设置的地方 } private static void setAccessible0(AccessibleObject obj, boolean flag) throws SecurityException { if (obj instanceof Constructor && flag == true) { Constructor> c = (Constructor>)obj; if (c.getDeclaringClass() == Class.class) { throw new SecurityException("Cannot make a java.lang.Class" + " constructor accessible"); } } obj.override = flag; //field父类中的override属性设置为flag,也就是我们设置的true }
//set Feild中的set方法public void set(Object obj, Object value) throws IllegalArgumentException, IllegalAccessException { if (!override) {//同样是判断field父类中的override,为true则不检查对象访问权限 直接设值 if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) { Class> caller = Reflection.getCallerClass(); checkAccess(caller, clazz, obj, modifiers); } } getFieldAccessor(obj).set(obj, value); }
3. 函数参数的值传递和引用传递
java中的函数参数传递分为值传递和引用传递。实际上都可以认为是值传递(引用传递实质传递的是引用的值(可以看作C++中指针,存放地址的变量),也同样需要拷贝副本,不过不是对象的副本,而且存放对象地址的变量的副本)。
值传递:针对于基本数据类型
引用传递:针对于对象
int i = 1void add1 (int i){//值传递 在i参数传入的时候 实际上会拷贝i的一副本,然后在函数中进行操作 i++;//实际操作的是i的副本,所以函数外部的i变量不受影响}System.out.println(i);//输出为1Integer j = 1;// j = Integer.valueOf(1)void add2 (Integer j){//引用传递 在j参数传入的时候 实际上会拷贝j对象的引用 然后传入函数中进行操作 j++; //装箱和拆箱的等价? j=j+1 j = Integer.valueOf(j.intValue()+1) 实际上是副本j引用指向了新的Integer对象,外面的j引用还是指向原来的对象 }System.out.println(j);//输出同样为1
4. Integer原理及缓存机制
Integer是int基本数据类型的包装类,无非是在int基本类型的基础上增加了一些操作和其他属性。
Integer的实际对应int值是通过intValue()方法获取的,源码如下:
private final int value;//对应int基本类型的数值 是一个常量整型 public int intValue() { return value; }
前面说过的装箱用到的一个方法是valueOf(),让我们看看源码:
//可以看到传入的i 先和IntegerCache比较 在IntegerCache中则返回IntegerCache中的Integer不存在则new一个Integer对象 public static Integer valueOf(int i) { if (i >= IntegerCache.low && i <= IntegerCache.high) return IntegerCache.cache[i + (-IntegerCache.low)]; return new Integer(i); }
IntegerCache实现如下:
//IntegerCache是个Integer的内部类,在类加载的时候创建了256个缓存Integer对象,范围-128至127 private static class IntegerCache { static final int low = -128; static final int high; static final Integer cache[]; static { // high value may be configured by property int h = 127; String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { try { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); // Maximum array size is Integer.MAX_VALUE h = Math.min(i, Integer.MAX_VALUE - (-low) -1); } catch( NumberFormatException nfe) { // If the property cannot be parsed into an int, ignore it. } } high = h; cache = new Integer[(high - low) + 1]; int j = low; for(int k = 0; k < cache.length; k++) cache[k] = new Integer(j++); // range [-128, 127] must be interned (JLS7 5.1.7) assert IntegerCache.high >= 127; } private IntegerCache() {} }
知道Integer缓存的存在,下面我们看看下面的几个例子:
注:== 对象比较比较的是对象的引用是否相等 equals则根据对象内部的实现情况进行比较
Integer i = 1; //i = Integer.valueOf(1) 取缓存对象 IntegerCache.cache[129]Integer j = 1; //j = Integer.valueOf(1) 取缓存对象 IntegerCache.cache[129]System.out.println(i == j);//输出true i 和j指向同一个对象
Integer i = 1; //i = Integer.valueOf(1) 取缓存对象 IntegerCache.cache[129]Integer j = new Integer(1); // 新创建一个对象System.out.println(i == j);//输出false i 和j指向的不是同一个对象
Integer i = 128; //i = Integer.valueOf(128) 不在缓存访问内 new Integer(128)Integer j = 128; //j = Integer.valueOf(128) 不在缓存访问内 new Integer(128)System.out.println(i == j); //输出false i 和j指向的不是同一个对象
所以Integer对象在比较是否相等的时候 不要用 == 用equals Integer内部实现了自己用equals,源码如下:
public boolean equals(Object obj) { if (obj instanceof Integer) { return value == ((Integer)obj).intValue(); } return false; }
5. 实现一个函数来交换参数的两个值
前面已经补充了一些Java的相关知识点了,现在我们就来实现一个函数来交换参数的两个值
public class Main { static void swap1(Integer a, Integer b){ //第3节已经说明了 这里函数中只是操作的引用副本,是不影响函数外a,b的变化的 Integer temp = a; a = b; b = temp; }//输出a=1 b=2 static void swap2(Integer a, Integer b) throws NoSuchFieldException, IllegalAccessException {//通过反射机制来修改引用指向对象的value值即改改变函数外a,b中的value数属性 Field field = Integer.class.getDeclaredField("value");//value是private final field.setAccessible(true);//绕过安全检查 //temp = 1 temp ->Integer.valueOf(a.intValue()) -> Integer.valueOf(1) ->IntegerCache.cache[129] Integer temp = a.intValue(); //public void set(Object obj, Object value) //a.value->Integer.valueOf(b.intValue()).intValue(); 修改a指向对象中value值为2 //a指向的是缓存中的IntegerCache.cache[129] 所以IntegerCache.cache[129] //中的value被修改成2 field.set(a, b.intValue()); //b -> IntegerCache.cache[130] //b.value->IntegerCache.cache[129].value 所以 IntegerCache.cache[130] IntegerCache.cache[129] value都是2 所以a b中value 都是2 field.set(b, temp); }//输出a=2 b=2 static void swap3(Integer a, Integer b) throws NoSuchFieldException, IllegalAccessException { Field field = Integer.class.getDeclaredField("value"); field.setAccessible(true);//绕过安全检查 Integer temp = new Integer(a.intValue());//和swap2唯一区区别就在这一行 field.set(a, b.intValue()); //a->Integer.valueOf(b.intValue()).intValue(); field.set(b,temp);//b.value->new Integer(a.intValue()).intValue() 而不是修改成IntegerCache.cache[129].intValue(); }//输出a=2 b=1 但是存在一个问题就是同样修改了IntegerCache中缓存值 后续存在隐患 public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException { Integer a = 1, b = 2; //装箱操作 等价于 Integer a = Integer.valueOf(1); Integer b = Integer.valueOf(2); swap1(a, b); System.out.println("a="+a+" b="+b);//输出a=1 b=2 }}