黑马程序员--Java反射
2015-08-11 18:00
363 查看
——- android培训、java培训、期待与您交流! ———-
学习反射首先需要了解类的加载,当程序使用某个类时,如果该类还未被加载到内存,则系统通过加载,连接,初始化三步来实现对这个类的初始化。
加载
指将class文件读入到内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
连接
验证是否有正确的内部结构,并和其他类协调一致
准备负责为类的静态成员分配内存,并设置默认初始化值。
解析将类的二进制数据中的符号引用替换为直接引用
初始化
就是通过构造方法进行初始化
类的加载器
负责将.class文件加载到内存中,并为之生成对应的Class对象。
类加载器的组成
Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,负责Java核心类的加载,例如System,String等。在JDK中JRE的lib目录下rt.jar文件中
Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录
System ClassLoader系统类加载器
负责在JVM启动时加载来自Java命令的class文件,以及classpath环境变量所指定的jar包和类路径。
Java反射机制是在运行状态中,对任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象,而解剖使用的就是Class类中的方法,所有先要获取到每一个字节码文件对应的Class类型的对象。
简单来说反射就是通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法。
使用反射首先必须得到class文件对象,其实也就是得到Class类的对象。
获取class文件对象的方式:
Object类的getClass()方法,返回是一个Class类。
数据类型的静态属性class
Class类中的静态方法:public static Class forName(String className),参数需要传入.class文件的绝对路径
推荐使用第三种,因为第三种是一个字符串,而不是一个具体的类名。这样我们就可以把这样的字符串配置到配置文件中。
使用反射获取这个类中的构造方法
通过得到的构造方法创建对象
利于反射获取私有修饰的构造方法并创建对象
有参无返和有参有返方法
反射越过泛型检查添加元素
有一个ArrayList<Integer>的对象,如果想要添加一个String类型的元素,可以使用反射。泛型只作用于代码的编译时期,而且此时add()方法的源码,参数是任意类型,并不是泛型。利用反射可以跳过泛型的检查,调用add()方法,直接添加到集合中去。
首先定义一个接口
然后创建一个该接口的实现类
使用Proxy类中的public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)方法创建一个代理对象。
此方法中第一参数为要代理对象的构造器,第二个参数为该对象要实现的接口列表,第三个参数为指派方法调用的调用处理程序。
第三个参数类型为InvocationHandler接口,实际需要的是实现该接口的实现类。所以继续定义一个实现了InvocationHandler接口的实现类。
得到此实现类以后,就可以创建新的代理对象
使用过程中需要注意修饰符问题,如果是私有,就用Constructor类的父类AccessibleObject中的setAccessible()方法取消访问检查。
获取构造方法,成员变量和成员方法的时候,分清楚各自的返回值以及参数类型。
一个典型的动态代理创建对象步骤:
创建一个实现IvocationHandler 接口的实现类
调用Proxy类的newProxyInstance方法传入参数获取动态代理对象
Proxy只支持interface代理,不过后面还有更强大的代理cglib。
学习反射首先需要了解类的加载,当程序使用某个类时,如果该类还未被加载到内存,则系统通过加载,连接,初始化三步来实现对这个类的初始化。
加载
指将class文件读入到内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象。
连接
验证是否有正确的内部结构,并和其他类协调一致
准备负责为类的静态成员分配内存,并设置默认初始化值。
解析将类的二进制数据中的符号引用替换为直接引用
初始化
就是通过构造方法进行初始化
类的加载器
负责将.class文件加载到内存中,并为之生成对应的Class对象。
类加载器的组成
Bootstrap ClassLoader 根类加载器
也被称为引导类加载器,负责Java核心类的加载,例如System,String等。在JDK中JRE的lib目录下rt.jar文件中
Extension ClassLoader 扩展类加载器
负责JRE的扩展目录中jar包的加载,在JDK中JRE的lib目录下ext目录
System ClassLoader系统类加载器
负责在JVM启动时加载来自Java命令的class文件,以及classpath环境变量所指定的jar包和类路径。
Java反射机制是在运行状态中,对任意一个类,都能够知道这个类的所有属性和方法,对于任意一个对象,都能够调用它的任意一个方法和属性,这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
要想解剖一个类,必须先要获取到该类的字节码文件对象,而解剖使用的就是Class类中的方法,所有先要获取到每一个字节码文件对应的Class类型的对象。
简单来说反射就是通过class文件对象,去使用该文件中的成员变量,构造方法,成员方法。
使用反射首先必须得到class文件对象,其实也就是得到Class类的对象。
获取class文件对象的方式:
Object类的getClass()方法,返回是一个Class类。
数据类型的静态属性class
Class类中的静态方法:public static Class forName(String className),参数需要传入.class文件的绝对路径
public class ClassDemo { public static void main(String[] args) throws ClassNotFoundException { // 方式1 Person p = new Person(); Class c = p.getClass(); // 方式2 Class c2 = Person.class; // 方式3 Class c3 = Class.forName("com.kxg_01.Person"); } }
推荐使用第三种,因为第三种是一个字符串,而不是一个具体的类名。这样我们就可以把这样的字符串配置到配置文件中。
获取构造方法
定义一个类package com.kxg_01; public class Person { private String name; int age; public String adress; public Person() { } Person(String adress, int age) { this.adress = adress; this.age = age; } private Person(int age) { this.age = age; } public Person(String name, int age, String adress) { this.name = name; this.adress = adress; this.age = age; } public void show() { System.out.println("show"); } public void show2(String s) { System.out.println("show2"+s); } public String show3(String s) { return "show3:" + s; } private void show4(String s) { System.out.println("show4"); } @Override public String toString() { return "Person [name=" + name + ", age=" + age + ", adress=" + adress + "]"; } }
使用反射获取这个类中的构造方法
package com.kxg_01; import java.lang.reflect.Constructor; public class ClassDemo2 { public static void main(String[] args) throws Exception{ // 获取字节码文件 Class c = Class.forName("com.kxg_01.Person"); // public Constructor[] getConstructors() // 返回一个包含某些 Constructor 对象的数组, // 这些对象反映此 Class 对象所表示的类的所有公共构造方法。也就是public修饰的构造方法 Constructor[] cons = c.getConstructors(); for (Constructor con : cons) { System.out.println(con); } System.out.println("------------------------------------"); // public Constructor[] getDeclaredConstructors() // 返回 Constructor 对象的一个数组, 这些对象反映此 Class 对象表示的类声明的所有构造方法。 // 它们是公共、保护、默认(包)访问和私有构造方法。 Constructor[] cons2 = c.getDeclaredConstructors(); for (Constructor con : cons2) { System.out.println(con); } // public Constructor getConstructor(Class<?>... parameterTypes) // 返回一个 Constructor 对象,它反映此 Class 对象所表示的类的指定公共构造方法 // 参数表示的是你要获取的构造方法的构造参数个数以及数据类型的class字节码文件对象 Constructor con = c.getConstructor(String.class, int.class); } }
通过得到的构造方法创建对象
package com.kxg_01; import java.lang.reflect.Constructor; public class ClassDemo3 { public static void main(String[] args) throws Exception{ // 使用Constructor类中的public T newInstance(Object... initargs)方法创建一个对象 Class c = Class.forName("com.kxg_01.Person"); // 获取无参构造,创建对象 Constructor con = c.getConstructor(); Object obj = con.newInstance(null); System.out.println(obj); // 获取有参构造,创建对象 Constructor con2 = c.getConstructor(String.class, int.class, String.class); Object obj2 = con2.newInstance("周星驰", 30, "中国"); System.out.println(obj2); } }
利于反射获取私有修饰的构造方法并创建对象
package com.kxg_01; import java.lang.reflect.Constructor; public class ClassDemo4 { public static void main(String[] args) throws Exception { // public Constructor getDeclaredConstructor(Class<?>... // parameterTypes) // Class类中的此方法可以获取任意修饰符修饰的构造方法 Class c = Class.forName("com.kxg_01.Person"); Constructor con = c.getDeclaredConstructor(int.class); // 用私有方法创建对象,需要取消 Java 语言访问检查 // Constructor类的父类中public void setAccessible(boolean flag) // 实现这个效果 con.setAccessible(true); Object obj = con.newInstance(30); System.out.println(obj); } }
获取成员变量
通过反射获取成员变量并进行赋值package com.kxg_02; import java.lang.reflect.Constructor; import java.lang.reflect.Field; public class ReflectDemo { public static void main(String[] args) throws Exception { // 获取字节码文件对象 Class c = Class.forName("com.kxg_01.Person"); // public Field[] getDeclaredFields():获取全部成员变量对象数组 Field[] fields = c.getDeclaredFields(); for (Field f : fields) { System.out.println(f); } System.out.println("-----------------------------"); // public Field getFields():获取成员变量对象数组,只能获取公共的 Field[] field = c.getFields(); for (Field f : field) { System.out.println(f); } // public Field getField(String name):获取单个指定成员变量 Field adressField = c.getField("adress"); // 对成员变量进行赋值 // 使用Field类中的public void set(Object obj,Object value)方法 // 将指定对象变量上此 Field 对象表示的字段设置为指定的新值 // 想要为一个成员变量进行赋值,首先需要有一个对象 Constructor con = c.getConstructor(); Object obj = con.newInstance(); adressField.set(obj, "中国");// 给obj对象的adressField字段赋值为中国 System.out.println(obj); } }
获取方法
获取无参无返回值方法并调用package com.kxg_02; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class MethodDemo { public static void main(String[] args) throws Exception { Class c = Class.forName("com.kxg_01.Person"); // public Method[] getMethods():获取除了私有修饰的所有方法,包括从父类继承过来的 Method[] methods = c.getMethods(); for (Method m : methods) { System.out.println(m); } System.out.println("-----------------------"); // public Method getMethod(String name,Class<?>... parameterTypes) // 第一个参数是方法名,第二个参数是该方法参数的class类型,没有就不写,或者写null Method m = c.getMethod("show", null); // public Object invoke(Object obj,Object... args) // 第一个参数是要调用方法的对象,第二个参数是该方法的实际参数,没有就不写,或者写null // 创建对象 Constructor con = c.getConstructor(); Object obj = con.newInstance(); // 调用方法 m.invoke(obj, null); } }
有参无返和有参有返方法
package com.kxg_02; import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class MethodDemo2 { public static void main(String[] args) throws Exception { // 获取字节码文件对象 Class c = Class.forName("com.kxg_01.Person"); // 创建无参对象 Constructor con = c.getConstructor(); Object obj = con.newInstance(); // 获取有参无返回值方法 Method m = c.getMethod("show2", String.class); // 获取有参有返回值方法 Method m2 = c.getMethod("show3", String.class); // 调用方法 m.invoke(obj, "中国"); String s = (String) m2.invoke(obj, "河南"); System.out.println(s); } }
反射越过泛型检查添加元素
有一个ArrayList<Integer>的对象,如果想要添加一个String类型的元素,可以使用反射。泛型只作用于代码的编译时期,而且此时add()方法的源码,参数是任意类型,并不是泛型。利用反射可以跳过泛型的检查,调用add()方法,直接添加到集合中去。
import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.ArrayList; public class ArrayListDemo { public static void main(String[] args) throws Exception { ArrayList<Integer> al = new ArrayList<Integer>(); // 得到al对象的字节码文件对象 Class c = al.getClass(); // 获取构造方法 Constructor con = c.getConstructor(); // 通过构造方法创建对象 Object obj = con.newInstance(); // 通过字节码对象获取add()方法 Method m = c.getMethod("add", Object.class); // 调用add()方法添加String类型元素 m.invoke(al, "跳过泛型添加元素"); System.out.println(al); } }
动态代理
Java中的java.lang.reflec包下提供了Proxy类和一个InvocationHandler接口,通过使用这个类和接口可以生成动态代理对象,JDK提供的代理只能针对接口做代理。首先定义一个接口
public interface Student { public void login(); public void regist(); }
然后创建一个该接口的实现类
public class StudentImpl implements Student { public void login() { System.out.println("登录功能"); } public void regist() { System.out.println("注册功能"); } }
使用Proxy类中的public static Object newProxyInstance(ClassLoader loader,Class[] interfaces,InvocationHandler h)方法创建一个代理对象。
此方法中第一参数为要代理对象的构造器,第二个参数为该对象要实现的接口列表,第三个参数为指派方法调用的调用处理程序。
第三个参数类型为InvocationHandler接口,实际需要的是实现该接口的实现类。所以继续定义一个实现了InvocationHandler接口的实现类。
import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; public class MyInvocationHandler implements InvocationHandler { private Object target;// 目标对象 public MyInvocationHandler(Object target) { this.target = target; } @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // 第一个参数是需要进行代理的对象,第二个参数是对象的方法,第三个参数是对象方法的参数 // 注意:该方法的调用是底层调用,看不到 System.out.println("权限校验"); Object result = method.invoke(target, args); System.out.println("日志记录"); return result;// 返回的就是代理对象 } }
得到此实现类以后,就可以创建新的代理对象
import java.lang.reflect.Proxy; public class Test { public static void main(String[] args) { // Student接口的实现类 Student s = new StudentImpl(); // InvocationHandler接口的实现类 MyInvocationHandler mi = new MyInvocationHandler(s); // 因为是对Student接口进行动态代理,所以返回值的类型也是Student接口 Student proxy = (Student) Proxy.newProxyInstance(s.getClass() .getClassLoader(), s.getClass().getInterfaces(), mi); // 调用方法 proxy.login(); proxy.regist(); } }
总结
反射这个部分需要理解的非常透彻,不同以往的创建对象方式,成员变量赋值以及方法的调用,感觉是这个部分最大的难点,思路要清晰,不要和之前的知识点混淆在一块。牢记反射中要用的几个重要的方法,例如获取指定构造方法,指定成员变量和指定方法。使用过程中需要注意修饰符问题,如果是私有,就用Constructor类的父类AccessibleObject中的setAccessible()方法取消访问检查。
获取构造方法,成员变量和成员方法的时候,分清楚各自的返回值以及参数类型。
一个典型的动态代理创建对象步骤:
创建一个实现IvocationHandler 接口的实现类
调用Proxy类的newProxyInstance方法传入参数获取动态代理对象
Proxy只支持interface代理,不过后面还有更强大的代理cglib。
相关文章推荐