目录
反射(reflection)
反射 是在不修改源码情况下 来控制程序 符合设计模式的ocp原则(开闭原则 :不修改 源码 扩容功能)
方法在反射中 也被视为对象 把java 的万物皆对象 在反射中体现出了
反射机制
-
反射机制允许程序在执行期间借助于Reflection API取得任何类的内部信息(比如构造方法 成员变量 成员方法等等),并能操作对象的属性及方法。反射在设计模式和框架底层都会用的到
-
加载完类之后,在堆中就产生了一个class类型的对象,(一个类只有一个class对象),这个对象包含了类的完整结构信息,通过这个对象得到类的结构,这个对象就像一面镜子,透过这个镜子看到类的结构,所以形象的称为:反射
java程序在计算机有三个阶段
计算机有三个阶段 代码阶段/编译阶段 class类阶段 运行阶段
由类加载器,来完成class类的生成
反射相关的类 在API java .lang.refection 中
反射相关的主要类:
java.lang.Class 代表一个类 Class 对象表示某个类加载后在堆中的对象
java.lang.reflect.Method 代表类的方法 Method对象表示某个类的方法
java.lang.reflect.Field :代表类的成员方法 Field对象代表某个类的成员变量
java.lang.reflect..Constructor 代表类的构造方法 Constructor 对象代表某个类的构造器
反射的优缺点
优点 可以动态的创建和使用对象(也是框架的底层核心) 使用灵活没有反射机制 框架机制就失去底层支撑
缺点 使用反射基本就是解释执行 对执行速度有影响
反射机制的优化 ----- 关闭访问检查
-
Method Field 和 Constructor 对象都有setAccessible() 方法 Accessible 代表 无障碍 可使用的
-
setAccessible 作用是启动和禁用访问安全检查的开关
-
参数值为true 表示 反射的对象在使用是取消访问检查 提高反射效率 参数值为false 则表示反射的对象执行访问检查
双亲委派机制
双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器.每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载.
双亲委派模型工作过程:
1)当Application ClassLoader 收到一个类加载请求时,他首先不会自己去尝试加载这个类,而是将这个请求 委派给父类加载器Extension ClassLoader去完成.
2)当Extension ClassLoader收到一个类加载请求时,他首先也不会自己去尝试加载这个类,而是将请求委派 给父类加载器Bootstrap ClassLoader去完成.
3)如果Bootstrap ClassLoader加载失败,就会让Extension ClassLoader尝试加载.
4)如果Extension ClassLoader也加载失败,就会使用Application ClassLoader加载.
5)如果Application ClassLoader也加载失败,就会使用自定义加载器去尝试加载.
6)如果均加载失败,就会抛出ClassNotFoundException异常.
3.例子:
当一个Hello.class这样的文件要被加载时.不考虑我们自定义类加载器,首先会在AppClassLoader中检查是否加载过,如果有那就无需再加载了.如果没有,那么会拿到父加载器,然后调用父加载器的loadClass方法.父类中同理会先检查自己是否已经加载过,如果没有再往上.注意这个过程,直到到达Bootstrap classLoader之前,都是没有哪个加载器自己选择加载的.如果父加载器无法加载,会下沉到子加载器去加载,一直到最底层,如果没有任何加载器能加载,就会抛出ClassNotFoundException.
2.3 CLassLoader类
ClassLoader 叫做类加载器.虚拟机设计团队把类加载阶段中的“通过一个类的全限定名来获取描述此类的二进制字节流” 也就是.class字节码文件,这个动作放到java虚拟机外部去实现,以便让应用程序自己决定去如何获取所需要的类,实现这个动作的代模块称之为“类加载器”.把【.class】文件加载到jvm虚拟机当中,转换为一个Class的对象【类的字节码对象 类的类对象】
2.ClassLoader的方法:
static ClassLoader getSystemClassLoader()
返回用于委派的系统类加载器
ClassLoader getParent()
返回父类加载器进行委派
Class 类
Class 也是类 因此也继承 Object类
Class类对象不是new出来的 而是系统创建的
对于某个类的Class类对象 在内存中只有一份,因此类只加载一次
每个类的实例都会记得自己是由哪个Class实例所生成
通过Class可以完整地得到一个类的完整结构 通过一系列API
Class对是存放在堆中
类的字节码二进制数据,是放在方法区中,有的地方称为类的元数据(包括 方法代码 变量名 方法名 访问权限 等等)
Class类的常用方法
//演示 Class 类的常用方法
String classAllPath = "反射.Class_.Car";
//1.获取到Car 类 对应的Class对象 Class.forName(classAllPath)
//<?> 表示不确定的java类型
Class<?>cls = Class.forName(classAllPath);
//2 输出cls 显示cls对象,
System.out.println(cls);//是哪个类的Class对象 class 反射.Class_.Car
// 3 输出cls运行类型 cls.getClass()
System.out.println(cls.getClass());//class java.lang.Class
// 4 得到包名 cls.getPackage().getName()
System.out.println(cls.getPackage().getName());//反射.Class_
//5 得到全类名 cls.getName()
System.out.println(cls.getName());//反射.Class_.Car
// 6 通过cls创建 对象实例 newInstance()
Car car = (Car)cls.newInstance();
System.out.println(car);//car.toString Car{brand='宝马', price=500000, color='白色'}
// 7 通过反射获取属性 cls.getField("属性名");
Field brand = cls.getField("brand");
//brand.get(car) 获取 属性
System.out.println(brand.get(car));//宝马
// 8 通过反射给属性赋值
brand.set(car,"奔驰");
System.out.println(brand.get(car));//奔驰
// 9 得到所有的属性
System.out.println("所有的属性信息-============");
Field[] fields = cls.getFields();
for (Field field : fields) {
System.out.println(field.get(car));
}
}
获取Class类对象的方式(6种)
第一种
前提:已知一个全类名 且该类在类路径下 可通过Class类的静态方法 forNam("路径") 获取
应用场景:多用于配置文件, 读取类全路径, 加载类
// 方式一 Class.forName String classAllPath = "反射.Class_.Car"; Class<?> aClass1 = Class.forName(classAllPath); System.out.println(aClass1); //class 反射.Class_.Car
第二种
前提:若已知具体的类,通过类的class获取,该方法最为安全可靠程序性能最高
应用场景:多用于参数传递 比如通过反射得到对应的构造器对象
// 方式二 类名.class, 应用场景 :应用参数传递 Class<Car> aClass2 = Car.class; System.out.println(Car.class); //class 反射.Class_.Car
第三种
前提:已知某个类的实例 调用该实例的getClass()方法获取Class对象
应用场景:通过创建好的对象 获取Class对象
//第三种 通过对象的实例 获取Class对象 Car car = new Car(); Class<? extends Car> aClass3 = car.getClass(); System.out.println(aClass3); //class 反射.Class_.Car
第四种
通过类加载器 来获取到Class对象 拓展知识【类加载器有4种】
//1)先得到类加载 car ClassLoader classLoader = car.getClass().getClassLoader(); // 通过类加载得到 Class对象 Class<?> aclass4 = classLoader.loadClass(classAllPath); System.out.println(aclass4); //class 反射.Class_.Car
第五种
基本数据类型(int,char,boolean,double,yte,short) 按如下方式得到Class类对象
//第五种 基本数据类型(int,char,boolean,double,byte,short) 按如下方式得到Class类对象 Class<Integer> integerClass = int.class; Class<Character> characterClass = char.class; Class<Boolean> booleanClass = boolean.class; System.out.println(integerClass);//int
第六种
基本数据类型对应的包装类,可以通过 .TYPE 得到Class类对象
第六种 基本数据类型对应的包装类,可以通过 **.TYPE ** 得到Class类对象 //第六种 基本数据类型的包装类 可以通过 .TYPE 得到Class类对象 Class<Integer> type1 = Integer.TYPE; Class<Character> type2 = Character.TYPE; System.out.println(type1);//int
// 哈希值都一样 表示都是同一个 System.out.println(integerClass.hashCode());//1580066828 System.out.println(type1.hashCode());//1580066828
哪些类型有Class 对象
1、外部类,成员内部类,静态内部类,局部内部类,匿名内部类
2、interface:接口
3、数组
4、enum :枚举
5annotation、注解
6、基本数据类型
7、void
// 演示那些类型有Class对象 Class<String> cls1 = String.class;//外部类 System.out.println(cls1);// class java.lang.String Class<Serializable> cls2 = Serializable.class;//接口 System.out.println(cls2);//interface java.io.Serializable Class<Integer[]> cls3 = Integer[].class;//数组 System.out.println(cls3);//class [Ljava.lang.Integer; Class<Integer[][]> cls4 = Integer[][].class;//二维数组 System.out.println(cls4);//class [[Ljava.lang.Integer; Class<Deprecated> cls5 = Deprecated.class;//注解 System.out.println(cls5); // interface java.lang.Deprecated Class<Thread.State> cls6 = Thread.State.class;//枚举 System.out.println(cls6); //class java.lang.Thread$State Class<Long> cls7 = long.class;//基本数据类型 System.out.println(cls7); // long Class<Void> cls8 = void.class;//void数据类型 System.out.println(cls8); // void Class<Class> cls9 = Class.class;//Class类 System.out.println(cls9); //class java.lang.Class
类加载
反射机制是java实现动态语言的 关键,也就是通过反射实现类动态加载
1、静态加载 :编译时加载相关的类,如果没有则报错,依赖性太强
2、动态加载:运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性
类的加载时机
1、当创建对象时 (new)// 静态加载
2、当子类被加载时 // 父类也加载 //静态加载
3、调用类中的静态方法// 静态加载
4、通过反射 // 动态加载
类的加载流程图
类加载的五个阶段
加载阶段
JVM 在该阶段的主要目的是将字节码从不同的数据源(可能是class 文件 ,也可能是 jar包,甚至网络)转化成二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class 对象
2.1)连接阶段 -验证
目的是为了确保Class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
包括:文件格式验证(是否以魔数 oxcafebabe开头),元数据验证、字节码验证、符号引用验证
可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟墙类加载的时间
2.2)连接阶段 - 准备
JVM会在该阶段对静态变量,分配内存 并默认初始化(对应数据类型的默认初始值 ,如:0、0L、null、false等)。这些变量所使用的内存都健在方法区中进行分配
2.3)连接阶段 - 解析
虚拟机将常量池的符号引用替换为直接引用的过程
lnitalization (初始化)
到初始化阶段,才真正开始执行类中定义分java程序代码,此阶段是执行<clinit>()方法的过程
<clinit>()方法是由编译器按语句在源文件出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块的语句,并进行合并
虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步如果多个线程同时去初始化一个类,那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>()方法完毕
通过反射获取类的结构信息
第一组
//得到Class对象 Class<?> cls = Class.forName("反射.类加载.Person"); //cls.getName() 获取全类名 System.out.println(cls.getName()); //cls.getSimpleName() 获取简单类名 System.out.println(cls.getSimpleName()); //cls.getFields() 获取所有public 修饰的属性 ,包含本类及父类的 Field[] fields = cls.getFields(); //cls.getDeclaredFields() 获取[本类]中的所有属性 Field[] declaredFields = cls.getDeclaredFields(); //cls.getMethods() 获取所有public 修饰的方法 包含本类及父类的 Method[] methods = cls.getMethods(); //cls.getDeclaredMethods() 获取本类的所有方法 Method[] declaredMethods = cls.getDeclaredMethods(); //cls.getConstructors() 获取所有public 修饰的构造器 包含本类 Constructor<?>[] constructors = cls.getConstructors(); // cls.getDeclaredConstructors() 获取所有的本类构造器 Constructor<?>[] declaredConstructors = cls.getDeclaredConstructors(); //cls.getPackage() 以Package 的形式返回包信息 System.out.println(cls.getPackage()); //cls.getSuperclass() 以class形式返回 父类信息 Class<?> superclass = cls.getSuperclass(); // cls.getInterfaces() 以Class [] 形式返回接口信息 Class<?>[] interfaces = cls.getInterfaces(); //cls.getAnnotatedInterfaces() 以Annotation[]的形式返回注解信息 AnnotatedType[] annotatedInterfaces = cls.getAnnotatedInterfaces();
第二组
java.lang.reflect.Field 类
与属性 相关的方法 getModifiers: 以int 形式返回修饰符 【说明 :默认修饰符 是 0 ,public 是 1, private 是 2 ,protected 是 4 ,static 是8 final 是 16】 getType : 以Class 形式返回类型 getName : 返回属性名 Class<?> name = Class.forName("反射.类加载.Person"); Field[] fields = name.getDeclaredFields(); for (Field field : fields) { System.out.println("本类中的所有属性"+field.getName() +"该属性的修饰符"+field.getModifiers()+"该属性的类型"+field.getType()); } /* 本类中的所有属性name该属性的修饰符1该属性的类型class java.lang.String 本类中的所有属性job该属性的修饰符0该属性的类型class java.lang.String 本类中的所有属性age该属性的修饰符4该属性的类型int 本类中的所有属性sal该属性的修饰符2该属性的类型double */
第三组
java.lang.refiect.Method 类
与 方法 相关的方法 getModifiers:以int形式返回修饰符 【说明 :默认修饰符 是 0 ,public 是 1, private 是 2 ,protected 是 4 ,static 是8 final 是 16 】 getReturnType : 以Class 形式获取 返回类型 getName :返回方法名 getParameterType :以Class[]返回参数类型数组 Method[] declaredMethods = name.getDeclaredMethods(); for (Method declaredMethod : declaredMethods) { System.out.println("本类的所有方法"+declaredMethod.getName()+"该方法的修饰符=" +declaredMethod.getModifiers()+"该方法返回类型"+declaredMethod.getReturnType()); Class<?>[] types = declaredMethod.getParameterTypes(); for (Class<?> type : types) { System.out.println("该方法的形参类型"+type); } } /* 本类的所有方法m1该方法的修饰符=1该方法返回类型void 该方法的形参类型class java.lang.String 该方法的形参类型int 该方法的形参类型double 本类的所有方法m2该方法的修饰符=4该方法返回类型class java.lang.String 本类的所有方法m3该方法的修饰符=2该方法返回类型void 本类的所有方法m4该方法的修饰符=0该方法返回类型void */
第四组
java.lang.reflect.Constructor 类
与构造器 相关的方法 getModifiers:以int 形式返回修饰符 【说明 :默认修饰符 是 0 ,public 是 1】 getName : 返回构造器名(全类名) getParameterTypes :以Class[] 返回参数类型数组 for (Constructor<?> declaredConstructor : declaredConstructors) { System.out.println("本类中所有的构造器"+declaredConstructor.getName()+""); Class<?>[] parameterTypes = declaredConstructor.getParameterTypes(); for (Class<?> parameterType : parameterTypes) { System.out.println("该构造器的形参类型"+parameterType); } } /* 本类中所有的构造器反射.类加载.Person 该构造器的形参类型class java.lang.String 本类中所有的构造器反射.类加载.Person */
通过反射创建对象
方式一:
调用类中的public 修饰的无参构造器
方式二:
调用类中的指定构造器
方式三 Class类相关的方法:
newlnstance :调用类中的无参构造器 获取对应类的对象
getConstructor(Class..clazz) :根据参数列表,获取对应的构造器对象
getDecalaredConstructor(Class...clazz):调用参数列表 ,获取对应的构造器对象
方式四 Constructor 类相关方法:
setAccessible :爆破 爆破反射 打破私有权限
newlnstance(Object...obj) :调用构造器
反射爆破操作
根据属性名获取Field对象
Field f = clazz对象.getDeclaredField(属性名)
爆破 :f.setAccessible(true);
访问
f.set(o,值)//o表示对象
sout(f.get(o))//o
注意 :如果是静态属性 则set和get中的参数o 可以写成 null
访问方法
根据方法名和参数列表获取Method方法对象 :
Method m = clazz.getDeclaredMethod(方法名.XX.class)//得到本类所有方法
获取对象 :Object o = clazz.newlnstance();
爆破 m.setAccessible(true);
访问 :Object obj = m.invoke(o,实参列表)
注意 如果是静态方法 , 则invoke的参数o 可以写成 null