一,反射【是框架的灵魂】
- 概述:在程序运行过程中可以对任意类型中的任意资源进行操作,这种动态获取或操作资源的行为就叫做反射。
- 场景:在不知道操作类型的基础进行操作采用反射,只有在要运行的时候才知道类型。
- 反射就是正向运行的逆向过程。
- 正向:编写源代码---> 编译成字节码文件---->jvm加载字节码文件启动运行--->创建对象或类名--->调用对应的方法和属性运行
- 反射:代码运行的过程中直接获取到字节码文件对象--->通过字节码文件对象获取对应的元素的对象--->通过元素对象调用对应的方法开始执行功能
- 字节码文件对象:字节码文件在内存中的对象表现形式【java中使用一个Class类对字节码文件这种事物进行相关特征和行为的描述 Class的对象就是jvm加载字节码文件的体现】
- 【元素:指的是描述成员变量、成员方法、构造方法特征和行为的类】
- jvm加载.class 文件的处理机制:
- jvm通过类加载器将字节码文件以对象的形式加载到内存中【在方法区中创建了一个Class类型的对象】,jvm加载之后对这个Class对象的内容进行分类管理【属性、成员方法,构造方法】,jvm分完类发现内容各自有各自的共同特征,把这些类别内容分别的进行描述形成对应的类型【属性对应的类型Filed、方法对应的类 Method、构造对应的Constract】jvm把管理着三个类的权限给Class对象来管理。
- 反射对象操作的元素除了对应的类对象还有Filed对象 Method对象 Constract对象。
- 元素的操作变成了自己的对象操作自己和所属的类对象没有关系,实现解耦合。
- 执行的顺序:jvm加载字节码文件创建Class对象,Class对象去统筹属性、方法、构造各自类的对象来进行具体的操作。
- 比如:使用反射执行一个方法
- Class对象直接获取方法的对象,使用这个方法的对象调用对应的执行方法直接执行。
- Class:他就是字节码文件在内存中的虚拟体现【字节码文件在内存中的具体描述】每一个字节码文件都对应有一个Class对象而且是唯一的。
- 反射的好处:
- 解耦合,利于编程
- 提高代码的维护性。【实现代码和维护的分离】
- 使用反射技术的步骤:
- 获取Class对象【获取那个字节码文件的对象】
- 通过字节码文件对象获取要操作的元素的对象、
- 使用元素对象调用对应的方法来执行相关的功能
二,获取类的字节码对象的三种方式
- 第一种方式:对象名称.getClass():直接获取对象对应类型的字节码文件对象
- 第二种方式:类名.class :直接获取到类型的字节码文件对象
- 第三种方式:Class.forName(String pathName) : 根据 pathName 来创建对应类型的字节码文件对象
说明:pathName:指一个类的全名称【全限定类名:包路径+类名】
代码示例
//定义一个Person类public class Person {
String name;
int age;
public Person(String name) {
super();
this.name = name;
}
public Person() {
super();
}
public Person(String name, int age) {
super();
this.name = name;
this.age = age;
}
public Person(int age) {
super();
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + "]";
}
}
//定义一个测试类public class Demo_Class {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//使用反射技术 得到Person类的字节码文件的对象
//第一种方式 对象法
Person p = new Person();
//clazz1 是class类的对象 [它person.class这个文件在内存中的对象形式]
//class类是所有字节码文件内存对象对应的类型 [一个class的对象对应一个.class为后缀名的文件]
//class的对象是内存中的字节码文件资源的体现,.class文件是磁盘中字节码资源的体现
//参考 file类 file类是所有磁盘文件在内存在的对象对应的类型
Class clazz1 = p.getClass(); //对象名.getClass()得到内存中字节码文件对象
//第二种方式 类名表示.class 文件 ;类名.class 就是字节码文件对象
Class clazz2 = Person.class;
//第三种方式 使用全限定类名 [类字节码文件所在的包路径] 得到字节码文件对象
Class<?>clazz3 = Class.forName("com.ujiuye.demo.Person");
}
}
三,Class类实例化对象的方法
- 字节码文件对象:指字节码文件在内存中对应的 Class类型 对象
- 字节码文件实例对象:指字节码文件对应类的对象
- newInstance():实例化字节码文件对应类型的具体实例对象
属于Class类的方法:
代码示例
public class Demo_Class {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
//使用反射技术 得到Person类的字节码文件的对象
//对象法
Person p = new Person();
//clazz1 是class类的对象 [它person.class这个文件在内存中的对象形式]
//class类是所有字节码文件内存对象对应的类型 [一个class的对象对应一个.class为后缀名的文件]
//class的对象是内存中的字节码文件资源的体现,.class文件是磁盘中字节码资源的体现
//参考 file类 file类是所有磁盘文件在内存在的对象对应的类型
Class clazz1=p.getClass(); //对象名.getClass()得到内存中字节码文件对象
//第二种方式 类名表示.class 文件 ;类名.class 就是字节码文件对象
Class clazz2 =Person.class;
//第三种方式 使用全限定类名 [类字节码文件所在的包路径] 得到字节码文件对象
Class<?>clazz3 =Class.forName("com.ujiuye.demo.Person");
//通过字节码文件对象得到对应类的对象
Person person= (Person)clazz3.newInstance();
System.out.println(person);
}
}
注意事项:反射在实例化对象的时候默认走的是空参构造,使用反射技术的时候保证操作的类必须提供空参构造
四,榨汁机案例
- 榨汁机拥有榨汁的功能【需要传入水果】
- 水果有产出果汁的功能
- 分析:
- 榨汁机需要的水果【写榨汁机类:写一个方法榨汁 方法需要参数水果】
- 水果出果汁【写水果接口 共享方法出果汁】
- 设计几个具体的水果类,实现接口
代码示例1:【使用new对象的方式】
//定义一个方法 public class FruitMachine { // 榨果汁 public void getJuice(Fruit f) { f.flowJuice(); } }
//定义一个水果接口 public interface Fruit { // 出果汁 void flowJuice(); }
//定义一个苹果类实现接口 public class Apple implements Fruit{ @Override public void flowJuice() { System.out.println("苹果汁"); } }
//定义一个橘子类实现接口
public class Orange implements Fruit{ @Override public void flowJuice() { System.out.println("橘子汁"); } }
//定义测试类 public class Test { public static void main(String[] args) { FruitMachine machine = new FruitMachine(); // 想喝苹果汁 // Apple apple = new Apple(); // machine.getJuice(apple); Orange orange = new Orange(); machine.getJuice(orange); } }
- 总结:
- 开始喝的是苹果汁,如果想要喝橘子汁,只能把程序停下来,对代码进行修改修改后再重新运行程序。
- 修改程序,容易出错,有可能影响其他的地方,维护成本比较大
代码示例2:【使用反射方式】
import java.io.BufferedReader; import java.io.FileNotFoundException; import java.io.FileReader; public class Test { public static void main(String[] args) throws Exception { FruitMachine machine = new FruitMachine(); // 想喝苹果汁 // Apple apple = new Apple(); // machine.getJuice(apple); /*Orange orange = new Orange(); machine.getJuice(orange);*/ // 使用反射技术 // 首先使用熟悉的io流技术把文件中的内容读到 使用字符缓冲流一次读一行读到内容 BufferedReader br = new BufferedReader(new FileReader("a.properties"));
//a.properties文件里面的内容是 com.ujiuye.demo.Apple;com.ujiuye.demo.Orange
String name = br.readLine();// 是一个类的全限定类名
// 使用反射技术得到对应类的实例化对象
Class<?> clazz = Class.forName(name);
Fruit f =(Fruit) clazz.newInstance();
machine.getJuice(f);
br.close(); //关流
}
}
-
反射获取类中的公共构造方法并使用
- 如何获取构造方法对象?
- getContructor(Class params ):根据参数的类型及个数返回对应的构造方法对象
- 只能获取到公共 (public) 的构造方法
- 说明:
- params:指多个 class 类型的参数
- params:传参的时候传的是构造方法形参类型的字节码文件对象
- getContructors():返回字节码文件对象中所用的构造方法对象
- 如何控制构造方法创建类型对象
- 构造方法作用:创建对象并初始化值
- 使用Constructor类中的方法:
- newInstance():创建构造对象所在的字节码文件对象对应类型的实例化对象【叫构造方法自己执行自己】
代码示例
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
public class Demo_Constractor {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
//操作person类 先得到person的字节码文件对象
//有三种方式
Class<?> clazz = Class.forName("com.ujiuye.demo.Person");
//得到一个构造方法的对象 得到 满参构造
Constructor<?> c1 = clazz.getConstructor(String.class,int.class);
Constructor<?> c2 = clazz.getConstructor();
System.out.println(c1); //public com.ujiuye.demo.Person(java.lang.String,int)
//c1就是满参构造方法在内存中的一个对象形式
//想要获取类中所有的公共的构造方法对象
//person类里面4个构造方法的对象都罗列出来了
Constructor<?>[] contructors = clazz.getConstructors();
System.out.println(Arrays.toString(contructors));
//输出结果
//满参 public com.ujiuye.demo.Person(java.lang.String,int)
//age public com.ujiuye.demo.Person(int)
//name public com.ujiuye.demo.Person(java.lang.String)
//空参 public com.ujiuye.demo.Person()
//构造方法的作用是什么? 创建对象 给属性赋值
Person p1 = (Person)c1.newInstance("baobao",30);
System.out.println(p1); //Person [name=baobao, age=30]
Person p2 =(Person)c2.newInstance(); //因为c2是空参所有输出来是null值
System.out.println(p2); //Person [name=null, age=0]
}
}
- <!--记住一句话,Class对象在调用方法时,方法的实参都是字节码文件对象-->
-
反射获取类中的成员变量并使用
- 反射如何获取公共权限成员变量【Field】对象?
- getField(String name):根据给定的属性名称获取对应属性的对象
- getFields():获取所有公共的属性对象
- 只能获取到 public 修饰的属性对象
- 说明:
- name:是指属性名【字段名】
- 如何来获取属性对象里面的属性值?
- 使用Field类中的方法:get(Object o):获取到指定对象的属性值
- 说明:
- o:指定对象【getFiled的调用字节码对象的实例对象】
- 如何设定属性值使用Field里面的方法:
- set(Object o,Object V):给指定对象的指定属性对象赋值。
- 参数说明:
- o: 指定对象【getFiled的调用字节码对象实例对象】
- V:要赋值的新值
- 使用前提:属性公共(public)权限修饰才可以用。
代码示例
import java.lang.reflect.Field;
import java.util.Arrays;
public class Demo_Filed {
public static void main(String[] args) throws NoSuchFieldException, SecurityException, InstantiationException, IllegalAccessException {
//得到对应的类的反射对象 [Preson]
Class clazz= Person.class;
//获取对应的属性对象, 只能获取到public修饰的属性对象
Field f1 = clazz.getField("name");
Field f2 = clazz.getField("age");
System.out.println(f1); //public java.lang.String com.ujiuye.demo.Person.name
System.out.println(f2); //public int com.ujiuye.demo.Person.age
//获取所有公共的属性
Field[] fields = clazz.getFields();
System.out.println(Arrays.toString(fields));
//输出:[public java.lang.String com.ujiuye.demo.Person.name, public int com.ujiuye.demo.Person.age]
//属性的操作,获取值 设置值
Person person =(Person) clazz.newInstance();
String name =(String )f1.get(person); //name属性的对象对应的name属性属于哪个类对象 [Person类对象]
System.out.println(name); //空参,没有赋值 null
f1.set(person, "花花");
String name1 = (String)f1.get(person);
System.out.println(name1); //花花
}
}
-
获取类中的成员方法并且执行
- 如何成员方法对象:
- 使用Class类中的方法: getMethod(String name, Class<?>... parameterTypes):根据给定的名称和参数类型获取对应的方法对象
- 参数说明:
- name:方法名称
- parameterTypes:方法形参类型的字节码对象
- getMethods():获取所有的方法的对象
- 只能获取public修饰的方法
- 如何运行得到的方法的效果:
- 通过Method类中方法:invoke(Object obj, Object... args):使调用的方法对象的方法执行【让方法执行】
- 参数说明:
- obj:指定对象【getMethod 的调用字节码对象实例对象】
- args:方法需要的实参
代码示例
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Arrays;
public class Demo_Method {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
// 获取person类中所有的方法,先得到person类字节码文件对象
// 三种获取方式
Class clazz = Person.class;
Class<? extends Person> clazz2 = new Person().getClass();
Class<?> clazz3 = Class.forName("com.ujiuye.demo.Person");
// 获取想要操作方法的对象
// getname是空参所有后面的参数可以不用
Method m1 = clazz.getMethod("getName");
// setName有参数,后面跟上参数类型.class
Method m2 = clazz.getMethod("setName", String.class);
System.out.println(m1); // public java.lang.String com.ujiuye.demo.Person.getName()
System.out.println(m2); // public void com.ujiuye.demo.Person.setName(java.lang.String)
Method[] methods = clazz.getMethods();
// 遍历所有的方法
for (Method method : methods) {
System.out.println(method);
}
System.out.println(Arrays.toString(methods));
// 输出的是所有的方法包括object父类的方法
//方法用来干嘛?调用的 方法的对象得到了,想要方法对象对应的方法执行
Person person = (Person) clazz.newInstance();
String name = (String) m1.invoke(person);
System.out.println(name); // 得到的值是null值
//要执行哪个方法就用哪个方法对象去invoke
m2.invoke(person, "花花");
//获取值m1 get方法
String name1=(String)m1.invoke(person);
System.out.println(name1); //花花
}
}
五,暴力反射
- 通过Class类中:
getDeclaredXxx方法:可以获取类中的所有声明的成员(属性、方法、内部类),私有的成员也可以获取到。
Xxx代表 Field Constractor Method
- 修改该对象的访问权限:通过AccessibleObject类中的
- setAccessible(boolean b):改变对象的访问权限
- isAccessible():判断对象的是否可以访问
代码示例
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class Demo_Declared {
public static void main(String[] args) throws NoSuchFieldException, SecurityException, NoSuchMethodException {
//得到反射对象
Class clazz = Person.class;
/*Field name= clazz.getField("name");
Field age = clazz.getField("age");
System.out.println(name); //报错
System.out.println(age); //报错
*/
//暴力反射方法,访问属性
//person类里面的属性默认的或私有的都可以获取到
Field name = clazz.getDeclaredField("name");
Field age = clazz.getDeclaredField("age");
//增加这一步,把权限修饰符给改了,100%保证暴力的访问到
name.setAccessible(true);
System.out.println(name); //private java.lang.String com.ujiuye.demo.Person.name
System.out.println(age); //private int com.ujiuye.demo.Person.age
System.out.println(name.isAccessible()); //true
//暴力反射方法,访问构造方法
Method getName = clazz.getDeclaredMethod("getName");
System.out.println(getName); //private java.lang.String com.ujiuye.demo.Person.getName()
}
}
- 暴力反射的步骤:
- 获取字节码文件对象
- 采用getDeclaredXxx方法获取到操作的的对象【解决获取不到操作对象的问题】
- 使用setAccessible(true)方法改变操作对象访问的权限【解决了操作不了问题】
- 使用操作对象的内部功能进行具体操作。
-
练习
- //有如下集合
- ArrayList<Integer> list = new ArrayList<>();
- list.add(666);
- //设计代码,将字符串类型的"abc",添加到上述带有Integer泛型的集合list中
- 分析:正向编程的时候放不进去,因为list集合指定泛型为Integer,在编译的过程中这个指定的泛型是起作用的,他是要去对放进来的数据进行类型和范围的审核,符合泛型类型的要求可以添加,不符合直接报错不让添加。泛型只在编译时期起作用,一旦类开始运行的时候失去作用。类加载到方法区泛型就消失。我可以在集合字节码文件被加载到方法区以后去给list添加元素,就不会有泛型的约束,就可以添加进去了。
- 能做到运行时进行添加操作的只能是反射技术。采用反射来实现
代码示例
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Test_02 {
public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
ArrayList<Integer>list = new ArrayList<>();
list.add(666);
//list.add("abc"); //编译阶段泛型约束着添加的元素的数据类型
//获取list集合字节码文件对象
Class<? extends ArrayList> clazz = list.getClass();
//通过反射对象获取 add方法的对象
Method add = clazz.getDeclaredMethod("add", Object.class);
//执行add方法添加"abc"字符串
add.invoke(list, "abc");
System.out.println(list); //[666, abc]
}
}
- 注意:
- 泛型的擦除:到了运行时期泛型就不起作用【相当于消失】
- 泛型只在编译时期起作用叫做伪泛型