反射笔记
一、反射初接触
定义:将类中的各个组成部分封装为对象
比如:
- 构造器对象Constructor
- 成员变量对象Field
- 成员方法对象Method
好处
- 可以在程序运行过程中操作这些对象
- 解耦合,提高程序的可扩展性
获取类的Class对象的三种方式
- 【SOURCE 源代码阶段】
Class.forName("packageName.className");
将字节码文件加载如内存,返回Class对象【路径是全路径】 - 【CLASS 类加载器已经执行】
className.class;
:已经编译,通过类名的属性class获取Class对象 - 【RUNTIME 运行期实例阶段】
Object.getClass();
已经创建了对象,使用对象的方法getClass() 获取Class对象【这是Object类定义的方法】
- 【SOURCE 源代码阶段】
实现
//Demo01Reflect.java //1. 获取class对象第一种方式: Class.forName() 【已经编译】 //注意!!参数为字符串类型,传递的是类的全路径 Class personClass = Class.forName("com.rapjoee.day01.domain.Person"); System.out.println("第一种方式:" + personClass); //2. 第二种方式:类名.class Class personClass1 = Person.class; System.out.println("第二种方式:" + personClass1); //3.第三种方式: 已经创建了对象,则使用对象的getClass()方法 Person person0 = new Person(); Class personClass2 = person0.getClass(); System.out.println("第三种方式:" + personClass2); //判断下几个class对象是否是同一个 【是同一个】 //true System.out.println(personClass == personClass1); System.out.println(personClass2 == personClass1);
备注
- 同一个字节码文件(.class)在一次出现运行过程中,只会加载一次,不论哪种方式获得的Class对象是同一个【不同的字节码文件对应的Class对象是不同的】
- 适用性
- 第一种方式多应用于配置文件。可以把类名定义在配置文件中,读取文件。传递的是字符串
- 第二种方式多用于参数的传递
- 第三种方式多用于已经获取对象后,对象的字节码文件获取
二、Class对象的功能之一 -- 获取类的成员变量们
通过类的Class对象可以获取其成员变量的Field对象
方法
Field getField(String name)
:获取指定名称的public修饰的成员变量【此类方法不能获取public关键字之外修饰的成员变量】Field[] getFields()
:获取所有public修饰的成员变量获取后的操作: 1. 设置值:`Object get(Object obj)` 返回该所表示的字段的值 Field ,指定的对象上。 2. 获取值:`void set(Object obj, Object value)` 将指定对象参数上的此 Field对象表示的字段设置为指定的新值。 3. `setAccessible(boolean x)` 参数为true则忽略访问修饰符的安全检查【暴力反射】
Field getDeclaredField(String name)
:获取指定名称的成员变量,不考虑修饰符Field[] getDeclaredFields()
:获取所有的成员变量,不考虑修饰符
实现
//获取Person类的Class对象 Class personClass = Person.class; //获取成员变量的方法 //Field getField(String name):获取指定名称的public修饰的成员变量 Field a = personClass.getField("a"); System.out.println("getField():" + a); //对获取得到的成员变量进行操作 Person person0 = new Person(); a.set(person0, "Smith"); Object o = a.get(person0); System.out.println("get():" + o + " person0:" + person0); //Field[] getFields() Field[] fields = personClass.getFields(); System.out.println("getFields():" + Arrays.toString(fields)); //对获取的成员变量值进行操作 System.out.println("=========================================="); //Field getDeclaredField(String name) Field[] declaredFields = personClass.getDeclaredFields(); for (Field declaredField : declaredFields) { System.out.println(declaredField); } //Field[] getDeclaredFields() Field name = personClass.getDeclaredField("name"); //忽略访问修饰符的安全检查,不然下面的代码会抛出异常【private修饰中】 //暴力反射 name.setAccessible(true); name.set(person0, "吐槽星人"); System.out.println("name.get(person1):" + name.get(person0)); System.out.println("name:" + name); //Person类的成员变量 private String name; private int age; public String a; public String b;
三、Class对象的功能之二 -- 获取类的构造器们
通过类的Class对象获取类的构造器对象Constructor
方法
Constructor<T> getConstructor(class<?>... parameterTypes)
获取指定参数类型的被public关键字修饰的构造器
比如获取
public Person(String name, int age){}
构造器的写法为Constructor cons = personClass. getConstructor(String.class, int.class);
Constructor<?>[] getConstructors()
:获取被public修饰的构造器们Constructor<T> getDeclaredConstructor(class<?>... parameterTypes)
:获取所有的指定参数类型的构造器,不考虑访问控制修饰符Constructor<?>[] getDeclaredConstructors()
:获取所有的构造器,忽略修饰符获取构造器之后可以进行的操作: 创建对象
T newInstance(Object... initargs)
使用此 Constructor对象表示的构造函数,使用指定的初始化参数来创建和初始化构造函数的声明类的新实例。
这里我们知道,如果使用空参构造创建对象,可以使用newInstance()方法进行构造达到一样的效果
Object o = constructor.newInstance("Smith", 123);
实现
//获取一个Class对象 Class personClass = Person.class; // Constructor<T> getConstructor(类<?>... parameterTypes) //构造器的区别就是传递的参数不同,这里我们对该方法传递String 和 int Constructor constructor = personClass.getConstructor(String.class, int.class); System.out.println("constructor --> " + constructor); //获取构造器后,对其操作 //创建对象 //这里如果获取的 constructor 在第34行没有参数,则此处也没有参数,就是一个空参的构造器 Object o = constructor.newInstance("Smith", 123); System.out.println("o --> " + o); //Person类的构造器 private Person(String a) { this.a = a; } public Person(String name, int age) { this.name = name; this.age = age; }
四、Class对象的功能之三 -- 获取类的类/包与方法们
通过类的Class对象获取类的构造器对象Method
方法
Method getMethod(String name, class<?>... parameterTypes)
:获取指定名称与指定参数类型的方法Method[] getMethods()
Method getDeclaredMethod(String name, class<?>... parameterTypes)
Method[] getDeclaredMethods()
:获取所有的方法【包括自定义类中的与Object类定义的】- 获取方法对象后可以执行的操作
Object invoke(Object obj, Object... args)
:Method类的invoke方法可以用来执行方法。其参数:- Object obj:真实的对象
- Object... args:参数列表
String getName()
:获取方法的名称
String getName()
获取的是类的全路径【com.rapjoee.day01.domain.Person 】packageName getPackage()
获取这个类的全包名【 package com.rapjoee.day01.domain 】
实现
Class personClass = Person.class; // Method getMethod(String name, class<?>... parameterTypes) //获取指定名称的方法,传递1.方法名 2. 参数列表可变参数【一个方法的要素:方法名、参数】 //获取一个无参的方法Method对象 Method speak = personClass.getMethod("speak"); //对获取的Method对象进行操作【使用】 //先准备一个对象 Object person = personClass.newInstance(); speak.invoke(person); System.out.println("======================================="); //获取到一个带参的Method对象 //方法参数类型是String ,所以获取时传递参数类型为String.class Method eat = personClass.getMethod("eat", String.class); //调用Method类的invoke方法执行方法eat,传递eat方法的参数 eat.invoke(person, "Apple"); //Person类的两个测试方法 public void eat(String food) { System.out.println("The eat method is executed... --> " + food); } public void speak() { System.out.println("The speak method is executed..."); }
五、反射小练习
需求:
写一个"框架", 不改变该类的任意代码的前提下,可以帮我们创建任意类的对象,并且执行其中的任意的方法
思路
- 要求不改变代码的内容,可以把类等的信息放在配置文件夹中,其中配置类名与方法名等等
- 获取配置文件,拿到其中配置的内容
- 使用反射获取该类的Class对象、Method对象等等
- 利用构造器对象可以创建对象、利用方法对象可以执行方法等等
代码实现
ReflectPractice.java
//获取本类的类加载器 ClassLoader对象 ClassLoader classLoader = ReflectPractice.class.getClassLoader(); //找到配置文件,以输入流的形式返回 InputStream proInputStream = classLoader.getResourceAsStream("pro.properties"); //加载配置文件进入内存--创建Properties集合 Properties prop = new Properties(); try { //把配置文件信息装载进入内存中刚创建的集合中 prop.load(proInputStream); //获取配置文件中的类名与方法名信息 //【Person类的eat方法,带一String参/无参】 String className = prop.getProperty("className"); String methodName = prop.getProperty("methodName"); //使用反射机制获取拿到的类名的Class对象 Class<?> cls = Class.forName(className); //获取方法对象 Method methodEatWithFood = cls.getMethod(methodName, String.class); Method methodWithoutFood = cls.getMethod(methodName); //获取一个带int和String参数的构造器对象 Constructor<?> constructor = cls.getConstructor(String.class, int.class); //利用上面这个构造器创建一个带参对象,可以打印下其属性 Object obj = constructor.newInstance("地星人", 33); if (obj instanceof Person) { Person person = (Person) obj; System.out.println("person.getAge() : " + person.getAge() + "\r\nperson.getName(): " + person.getName()); } //执行方法,传递一个默认构造的对象 Object o = cls.newInstance(); methodWithoutFood.invoke(o);methodEatWithFood.invoke(o, "香蕉"); } catch (IOException | ClassNotFoundException | NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); }finally { //输入流使用完毕释放资源 try { proInputStream.close(); } catch (IOException e) { e.printStackTrace();}}
Person.java【com.rapjoee.day01.domain.Person】
public void eat(String food) { System.out.println("带参 eat 方法执行... --> " + food);} public void eat() { System.out.println("无参 eat 方法执行 --> no food!!");} public void speak() { System.out.println("The speak method is executed...");}
配置文件pro.properties
className = com.rapjoee.day01.domain.Person methodName = eat
备注
后期需要改动不用动大规模的代码,改动配置文件即可