一 反射的概述
- 什么是反射?在程序运行中分析类的一种能力
反射能做什么?(1)分析类:加载并初始化一个类,查看类的所有属性和方法 (2)查看并使用对象:查看一 个对象的所有属性和方法,使用对象的任意属性和方法
- 反射的应用场景:(1)构造通用的工具类时 (2)搭建具有高度灵活性和扩展性的系统框架
二 类加载器(ClassLoader)
- 作用:负责将类的字节码文件(.class文件)加载到内存中,并生成对应的Class文件
- Class对象:一个后缀名为 .java的源文件对应一个后缀名为 .class的字节码文件,一个字节码文件又对应一个Class对象
- 类加载时机:
(1)创建类的实例: Student stu = new Student(); //使用类加载器,将Student类的字节码文件加载到内存中 注:如果再次使用该方法创建Student类对象,类加载器将不再加载该类的字节码文件,因为一个类的字节码文件只会被加载一次 (2)访问类的静态成员时: Calendar.getInstance(); //使用类加载器,将Calendar类的字节码文件加载到内存中 (3)初始化类的子类: class User extends Person{} User user = new User(); //使用类加载器,先加载父类Person类的文件,再加载子类User类文件 (4)反射方式创建类的Class对象: Class clazz = Class.forname("类的正名"); 类的正名:包名 + 类名:如 javatest.Test 总结:第一次使用类中的成员时,类加载器就会将该类的字节码文件加载到内存中
三 获取Class对象的方式
1 使用Object类中的getClass()方法:Class clazz = 对象名.getClass();
2 类的静态属性:Class clazz = 类名.class;
3 Class类的静态方法:Class clazz = Class.forName("类的正名");
四 通过反射方式获取构造方法并使用
- Constructor<T>:构造器对象 :即其对应类的构造方法
- 通过Class对象获取构造器对象
1 getConstructor(Class<?>... parameterTypes):根据参数列表,获取对应的构造器对象(即对应类的构造方法)仅限公共的构造方法。 其中Class<?>...:可变的参数,代表Class类型的数组, ?:通配符,代表不确定的任意类型,根据构造方法进行对应,即Class<?>... parameterTypes 代表参数类别 2 getDeclaredConstructor(Class<?>... parameterTypes):根据参数列表,获取对应的构造器对象,可获得私有的构造方法 3 getConstructors():直接获取此类所有的构造方法(不报含私有方法) 4 getDeclaredConstructors():直接获取此类所有的构造方法 例:Class<?>... parameterTypes 若参数为 String name , 则为clazz.getConstructor(String.class); 若参数为 int age , 则为clazz.getConstructor(int.class);
- Constructor的常用方法
1 getName(): 获取构造函数名:以字符串形式返回此构造函数的名称 2 newInstance(Object... initargs): 根据此构造函数和指定参数创建对象,返回为Object类型对象,需要下转型 3 在调用私有构造方法创建对象是需要结合: Constructor对象名.setAccessible(boolean flag); + Constructor对象名.newInstance(Object... initargs); 当flag为true是,开始暴力反射,即可以调用私有构造方法
- 代码演示:
Person类 package javatest; //Person类 public class Person { //公共的无参构造 public Person() {} //公共的带参构造 public Person(String name){ System.out.println(name); } //私有的的带参构造 private Person(int age){ System.out.println(age); } }
测试类: import java.lang.reflect.Constructor; public class Test { public static void main(String[] args) throws Exception { //需求通过反射的方式创建Person类型的对象并使用其构造方法 //若不需要反射:可直接 Person p = new Person(); //1.获取Person类的字节码文件对象 Class clazz = Class.forName("javatest.Person"); //文件可能不存在,抛出异常 //或Class clazz = Person.class; //通过;类名也可得到CLass对象 //2.根据第一步,获取到的字节码文件对象获取指定的构造器对象 //2.1获取公共的无参构造 Constructor con1 = clazz.getConstructor(); //可能不存在该类型参数列表的构造方法,所以要抛出异常 System.out.print("获取公共的无参构造: "); System.out.println(con1); //2.2获取公共的有参构造 Constructor con2 = clazz.getConstructor(String.class); //参数为String类型,需要String.class作为参数 System.out.print("获取公共的有参构造: "); System.out.println(con2); //2.3获取私有的有参构造 Constructor con3 = clazz.getDeclaredConstructor(int.class); //参数为int类型,需要int .class作为参数 System.out.print("获取私有的有参构造: "); System.out.println(con3); //2.4直接获取所有的公共构造 System.out.println("-------------------------------"); System.out.print("直接获取所有的公共构造: "); Constructor[] cons = clazz.getConstructors();//获取所有公共构造 //遍历cons数组 for (Constructor con : cons) { System.out.println(con); } //3.根据构造器对象和参数,创建对应的Person类对象 System.out.println("-------------------------------"); //首先根据获取构造器名字 System.out.print("获取构造器名字: "); System.out.println(con2.getName()); //根据con2构造器创建对象 System.out.print("通过公共带参构造方法,创建对象,并输出参数:"); Person p = (Person) con2.newInstance("张三");//默认为Object类型,通过下转型(强制转换)得到Person对象 //通过有无地址值,确定是否存在 System.out.print("p的地址为: "); System.out.println(p); } }
- 结果展示:
- 总结:通过”全类名“先获得Class对象,根据Class对象获得Constructor对象(构造器对象)通过Constructor对象可以创建Person类对象
五 通过反射方式获取成员方法并使用
- Method:方法对象:及其对应类的成员方法
- 通过Class对象获取方法
1 getMethod(String name, Class<?>...parameterTypes):返回一个Method对象,仅公共成员方法 其中:name方法名, Class<?>...parameterTypes方法的参数列表 2 getDeclaredMethod(String, Class<?>...):返回一个Method对象,可获取私有方法 3 getMethod():返回此类中的所有(不含私有)方法数组 4 getDeclaredMethods():返回此类中的所有方法数组 注:getMethod()得到父类的方法,即一定会得到Object类中的方法
- Method的常用方法
1 getName():放回方法名 2 invoke( Object obj, Object...args):在指定对象上调用此方法,参数为args 其中:obj代表对象名 , 返回值为Object类型 3 在调用私有方法是需要结合: Method对象名.setAccessible(boolean flag); + Method对象名.invoke( Object obj, Object...args); 当flag为true是,开始暴力反射,即可以调用私有方法
- 代码演示
Person类: package javatest; //Person类 public class Person { //公共的无参方法 public void show1(){ System.out.println("我是公共的无参方法"); } //公共的有参方法 public void show2(int a){ System.out.println("我是公共的有参方法,参数为" + a); } //私有的有参方法 private int show3(int a, int b){ System.out.println("我是私有的有参方法,参数和为" + (a + b)); return (a + b); } }
import java.lang.reflect.Constructor; import java.lang.reflect.Method; public class Test { public static void main(String[] args) throws Exception { //需求通过反射的方式获取Person类的成员方法并调用 //若不需要反射:可直接 Person p = new Person(); //1.获取Person类的字节码文件对象 Class clazz = Class.forName("javatest.Person"); //文件可能不存在,抛出异常 //或Class clazz = Person.class; //通过;类名也可得到CLass对象 //2.获取该类的构造器对象,然后创建Person对象 Constructor con = clazz.getConstructor(); //获得系统提供的无参构造方法 Person person = (Person) con.newInstance(); //向下转型,获得Person对象 //3.获取该类的成员方法对象,然后调用此方法 //3.1调用公共的无参方法 Method method1 = clazz.getMethod("show1"); //打印方法 System.out.println(method1); //打印方法名 System.out.println(method1.getName()); //调用此方法 method1.invoke(person); //person作为对象进行调用 System.out.println("-----------------------------"); //3.2调用公共的有参方法 Method method2 = clazz.getMethod("show2", int.class); //调用此方法 method2.invoke(person,2); //person作为对象进行调用, arge参数 System.out.println("-----------------------------"); //3.2调用私有的有参方法 Method method3 = clazz.getDeclaredMethod("show3", int.class, int.class); //调用此方法 method3.setAccessible(true); int sum = (int)method3.invoke(person,1,2); //person作为对象进行调用 System.out.println("sum:" + sum); System.out.println("-----------------------------"); //3.4一次性调用所有的公共方法 Method[] methods = clazz.getMethods(); for (Method method : methods) { System.out.println(method); //会得到Object类的方法 } } }
- 结果演示:可以看出只有俩个为Person类的公共方法,其余为Object类方法
- 总结:通过”全类名“先获得Class对象,根据Class对象获得Constructor对象(构造器对象)通过构造器创建Person类对象,通过Class对象获取方法,再通过method类方法结合Person类对象调用方法
六 通过反射方式获取成员变量并使用
- Field对象:域(属性、成员变量)对象,及其对应类的成员变量
- 通过Class对象获取属性
1 getField(String name):返回一个Field对象,仅公共属性 其中:name:属性名 2 getDeclaredField(String name):返回一个Field对象,可获取私有属性 3 getField():返回此类中的所有属性(不含私有)方法数组 4 getDeclaredFields():返回此类中的所有属性方法数组
- Field的常用方法
1 set(Object obj, Object value):设置obj对象的指定属性值为value 2 setAccessible(boolean flag):将此属性的可访问性设置为指定布尔值 3 get(Object obj):访问obj对象的属性 4 equals(Object obj):将此Field与指定的对象进行比较 5 getName():返回此Field对象表示的属性的名称
- 代码演示
Person类: package javatest; //Person类 public class Person { //公有的属性 public String name; //私有的属性 private int age; //重写toString方法为了方便打印各个属性值 public String toString() { return "Person{" + "name='" + name + '\'' + ", age=" + age + '}'; } }
import java.lang.reflect.Constructor; import java.lang.reflect.Field; public class Test { public static void main(String[] args) throws Exception{ //需求:通过反射获取成员变量并使用 //1.获取Person类的字节码文件对象 Class clazz = Class.forName("javatest.Person"); //或:Class clazz = Person.class; //2.通过字节码文件对象获取构造器对象,然后创建Person类对象 Constructor con = clazz.getConstructor(); Person person = (Person)con.newInstance(); //合并版 Person person = (Person)clazz.getConstructor().newInstance(); 链式编程 //3.设置Person类对象的属性值 Field field1 = clazz.getField("name"); field1.set(person, "张三"); Field age = clazz.getDeclaredField("age"); age.setAccessible(true); //将私有属性设置为可设置权限 age.set(person, 1); System.out.println(person); } }
- 结果演示:可以看出只有俩个为Person类的公共方法,其余为Object类方法
- 总结:通过”全类名“先获得Class对象,根据Class对象获得Constructor对象(构造器对象)通过构造器创建Person类对象,通过Class对象获取方法,再通过method类方法结合Person类对象调用方法