反射;获取类的字节码对象;Class类实例化对象;获取类中的公共构造方法/成员变量/成员方法并执行;暴力反射 (Java Day27)
一,反射【是框架的灵魂】
- 概述:在程序运行过程中可以对任意类型中的任意资源进行操作,这种动态获取或操作资源的行为就叫做反射。
- 场景:在不知道操作类型的基础进行操作采用反射,只有在要运行的时候才知道类型。
- 反射就是正向运行的逆向过程。
- 正向:编写源代码---> 编译成字节码文件---->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]
}
}
- 注意:
-  泛型的擦除:到了运行时期泛型就不起作用【相当于消失】
-  泛型只在编译时期起作用叫做伪泛型
 
                    
                     
                    
                 
                    
                
 
                
            
         
         浙公网安备 33010602011771号
浙公网安备 33010602011771号