十二、反射机制

一、反射的概念

反射是指对于任何一个类,在"运行的时候"都可以直接得到这个类全部成分。

反射的核心思想和关键就是得到:编译以后的class文件对象。
反射是在运行时获取类的字节码文件对象:然后可以解析类中的全部成分。

反射为一个类的全部成分都设计了一个类型来代表这个对象:

  • Class : 字节码文件的类型
  • Constructor : 构造器的类型
  • Field : 成员变量的类型
  • Method : 方法的类型

二、获取Class类对象

反射是通过先得到编译以后的Class类对象:字节码文件。
然后才可以得到类中的全部成分,进行一些功能设计。

Class类对象的获取有三种方式:

  • 1.类名.class。
  • 2.通过类的对象.getClass()方法。
  • 3.Class.forName("类的全限名")。

Class类的方法:

  • String getSimpleName(); 获得类名字符串:类名
  • String getName(); 获得类全名:包名+类名
public class ReflectDemo01 {
    public static void main(String[] args) throws Exception {
        // 反射的第一步永远是先得到类的Class文件对象: 字节码文件。
        // 1.类名.class
        Class c1 = Student.class;
        System.out.println(c1);

        // 2.对象.getClass()
        Student swk = new Student();
        Class c2 = swk.getClass();
        System.out.println(c2);

        // 3.Class.forName("类的全限名")
        // 直接去加载该类的class文件。
        Class c3 = Class.forName("com.itheima._03反射_获取Class类对象.Student");
        System.out.println(c3);

        System.out.println(c1.getSimpleName()); // 获取类名本身(简名)
        System.out.println(c1.getName()); // 获取类的全限名
        // Student s1 = (Student) c1.newInstance(); // 调用无参数构造器得到对象,被淘汰了!

    }
}

三、获取构造器对象

获取构造器方法

  1. Constructor getConstructor(Class... parameterTypes)
    根据参数匹配获取某个构造器,只能拿public修饰的构造器,几乎不用!

2. Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!

  1. Constructor[] getConstructors()
    获取所有的构造器,只能拿public修饰的构造器。几乎不用!!太弱了!

4. Constructor[] getDeclaredConstructors()
获取所有申明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!

public class TestStudent {
    // 1. getConstructors:
    // 获取全部的构造器:只能获取public修饰的构造器。
    // Constructor[] getConstructors()
    @Test
    public void getConstructors(){
        // a.反射第一步先得到Class类对象
        Class c = Student.class ;
        // b.getConstructors():定位全部构造器,只能拿public修饰的!
        Constructor[] cons = c.getConstructors();
        // c.遍历这些构造器
        for (Constructor con : cons) {
            System.out.println(con.getName()+"===>"+con.getParameterCount());
        }
    }

    // 2.getDeclaredConstructors():
    // 获取全部的构造器:只要你敢写,这里就能拿到,无所谓权限是否可及。
    @Test
    public void getDeclaredConstructors(){
        // a.反射第一步先得到Class类对象
        Class c = Student.class ;
        // b.getDeclaredConstructors():定位全部构造器,只要申明了就可以拿到
        Constructor[] cons = c.getDeclaredConstructors();
        // c.遍历这些构造器
        for (Constructor con : cons) {
            System.out.println(con.getName()+"===>"+con.getParameterCount());
        }
    }

    // 3.getConstructor(Class... parameterTypes)
    // 获取某个构造器:只能拿public修饰的某个构造器
    @Test
    public void getConstructor() throws Exception {
        // a.反射第一步先得到Class类对象
        Class c = Student.class ;
        // b.getConstructor():定位某个构造器,根据参数匹配,只能拿public修饰的!
        // Constructor con = c.getConstructor(); // 报错!
        Constructor con = c.getConstructor(String.class  , int.class); // 有参数的!!
        // c.构造器名称和参数
        System.out.println(con.getName()+"===>"+con.getParameterCount());
    }

    // 4.getDeclaredConstructor
    // 获取某个构造器:只要你敢写,这里就能拿到,无所谓权限是否可及。
    @Test
    public void getDeclaredConstructor() throws Exception {
        // a.反射第一步先得到Class类对象
        Class c = Student.class ;
        // b.getDeclaredConstructor():定位某个构造器,根据参数匹配,只要申明了就可以获取
        Constructor con = c.getDeclaredConstructor(); // 可以拿到!定位无参数构造器!
        //Constructor con = c.getDeclaredConstructor(String.class  , int.class); // 有参数的!!
        // c.构造器名称和参数
        System.out.println(con.getName()+"===>"+con.getParameterCount());
    }
}

Student类

public class Student {
    private String name ;
    private int age ;

    private Student(){
        System.out.println("无参数构造器被执行~~~~");
    }

    public Student(String name, int age) {
        System.out.println("有参数构造器被执行~~~~");
        this.name = name;
        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 "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}

通过构造器初始化对象

Constructor的API:

  • T newInstance(Object... initargs)
    创建对象,注入构造器需要的数据。

  • void setAccessible(true)
    修改访问权限,true代表暴力攻破权限,false表示保留不可访问权限(暴力反射)

  • getParameterCount()
    拿到构造器参数个数

步骤:

  1. 得到Class类对象
  2. 定位构造器对象
  3. 打开私有构造器访问权限
  4. 通过构造线初始化对象(newInstance)
public class TestStudent02 {
    // 1.调用无参数构造器得到一个类的对象返回。
    @Test
    public void createObj01() throws Exception {
        // a.反射第一步是先得到Class类对象
        Class c = Student.class ;
        // b.定位无参数构造器对象
        Constructor constructor = c.getDeclaredConstructor();
        // c.暴力打开私有构造器的访问权限
        constructor.setAccessible(true);
        // d.通过无参数构造器初始化对象返回
        Student swk = (Student) constructor.newInstance(); // 最终还是调用无参数构造器的!
        System.out.println(swk);
    }

    // 2.调用有参数构造器得到一个类的对象返回。
    @Test
    public void createObj02() throws Exception {
        // a.反射第一步是先得到Class类对象
        Class c = Student.class ;
        // b.定位有参数构造器对象
        Constructor constructor = c.getDeclaredConstructor(String.class , int.class);
        // c.通过无参数构造器初始化对象返回
        Student swk = (Student) constructor.newInstance("孙悟空",10000); // 最终还是调用有参数构造器的!
        System.out.println(swk);
    }
}

四、获取Class类的成员变量

方法

1、Field getField(String name);
      根据成员变量名获得对应Field对象,只能获得public修饰
2.Field getDeclaredField(String name);
      根据成员变量名获得对应Field对象,只要申明了就可以得到
3.Field[] getFields();
      获得所有的成员变量对应的Field对象,只能获得public的
4.Field[] getDeclaredFields();
      获得所有的成员变量对应的Field对象,只要申明了就可以得到

成员变量Field的API

  • getType()
    得到成员变量的类型

  • getName()
    得到成员变量的名字

public class FieldDemo {
    /**
     * 1.获取全部的成员变量。
     */
    @Test
    public void getDeclaredFields(){
        // a.先获取class类对象
        Class c = Dog.class;
        // b.获取全部申明的成员变量对象
        Field[] fields = c.getDeclaredFields();
        for (Field field : fields) {
            System.out.println(field.getName()+"===>"+field.getType());
        }
    }

    /**
        2.获取某个成员变量对象
     */
    @Test
    public void getDeclaredField() throws Exception {
        // a.先获取class类对象
        Class c = Dog.class;
        // b.定位某个成员变量对象 :根据名称定位!!
        Field ageF = c.getDeclaredField("age");
        System.out.println(ageF.getName()+"--->"+ageF.getType());
    }

}

Dog类

public class Dog {
    private String name;
    private int age ;
    private String color ;
    public static String school;
    public static final String SCHOOL_1 = "宠物学校";

    public Dog() {
    }

    public Dog(String name, int age, String color) {
        this.name = name;
        this.age = age;
        this.color = color;
    }

    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;
    }

    public String getColor() {
        return color;
    }

    public void setColor(String color) {
        this.color = color;
    }

    @Override
    public String toString() {
        return "Dog{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", color='" + color + '\'' +
                '}';
    }
}

成员变量取值赋值

成员变量Field的API

1. void set(Object obj, Object value):给对象注入某个成员变量数据
2. Object get(Object obj):获取对象的成员变量的值。
2. void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
4. Class getType(); 获取属性的类型,返回Class对象。
5. String getName(); 获取属性的名称。
public class FieldDemo02 {
    @Test
    public void setField() throws Exception {
        // a.反射的第一步获取Class类对象
        Class c = Dog.class ;
        // b.定位name成员变量
        Field nameF = c.getDeclaredField("name");
        // c.为这个成员变量赋值!
        Dog taiDi = new Dog();
        nameF.setAccessible(true); // 暴力反射!
        /**
         * 参数一:被赋值的对象。
         * 参数二:该成员变量的值。
         */
        nameF.set(taiDi , "勇敢的泰迪");
        System.out.println(taiDi);

        // d.获取成员变量的值
        String value = nameF.get(taiDi)+"";
        System.out.println(value);
    }
}

五、获取Method方法

方法

1、Method getMethod(String name,Class...args);
     根据方法名和参数类型获得对应的方法对象,只能获得public的

2、Method getDeclaredMethod(String name,Class...args);
     根据方法名和参数类型获得对应的方法对象,包括private的

3、Method[] getMethods();
     获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的

4、Method[] getDeclaredMethods();
     获得类中的所有成员方法对象,返回数组,只获得本类申明的方法。

方法Method的API

  • getParameterCount()
    得到方法的参数个数

  • getReturnType()
    得到方法的返回类型

Method的方法执行:

Object invoke(Object obj, Object... args)

参数一:触发的是哪个对象的方法执行。
参数二: args:调用方法时传递的实际参数

public class MethodDemo01 {
    /**
     * 1.获得类中的所有成员方法对象
     */
    @Test
    public void getDeclaredMethods(){
        // a.先获取class类对象
        Class c = Dog.class ;
        // b.获取全部申明的方法!
        Method[] methods = c.getDeclaredMethods();
        // c.遍历这些方法
        for (Method method : methods) {
            System.out.println(method.getName()+"====>"
                    + method.getParameterCount()+"===>" + method.getReturnType());
        }

    }
    /**
     * 2. 获取某个方法对象
     */
    @Test
    public void getDeclardMethod() throws Exception {
        // a.先获取class类对象
        Class c = Dog.class;
        // b.定位它的某个方法
        Method run = c.getDeclaredMethod("run");
        // c.触发方法执行!
        Dog jinMao = new Dog();
        Object rs = run.invoke(jinMao); // 触发jinMao对象的run()方法执行!
        System.out.println(rs);// 如果方法没有返回值,结果是null

        /**
         * 参数一:方法名称
         * 参数二:方法的参数个数和类型(可变参数!)
         */
        Method eat = c.getDeclaredMethod("eat",String.class);
        eat.setAccessible(true); // 暴力反射!
        /**
         * 参数一:被触发方法所在的对象
         * 参数二:方法需要的入参值
         */
        Object rs1 = eat.invoke(jinMao,"肉");
        System.out.println(rs1);// 如果方法没有返回值,结果是null
    }
}

六、破坏封装性与约束性

  • 反射可以破坏面向对象的封装性(暴力反射)。
  • 同时可以破坏泛型的约束性
public class ReflectDemo {
    public static void main(String[] args) throws Exception {
        // 泛型只能工作在编译阶段,运行阶段泛型就消失了,
        // 反射工作在运行时阶段。
        List<Double> scores = new ArrayList<>();
        scores.add(99.3);
        scores.add(199.3);
        scores.add(89.5);

        // 拓展:通过反射暴力的注入一个其他类型的数据进去。
        // a.先得到集合对象的Class文件对象
        Class c = scores.getClass();
        // b.从ArrayList的Class对象中定位add方法
        Method add = c.getDeclaredMethod("add", Object.class);
        // c.触发scores集合对象中的add执行(运行阶段,泛型不能约束了)
        add.invoke(scores,"波仔");

        System.out.println(scores);
    }
}

七、反射的作用

反射适合做通用技术框架的底层实现,在框架的底层源码中我们经常看到反射的影子。

例如:
Mybatis框架:
你给任何一个对象数据我都可以直接帮你解析字段并且把对应数据保存起来。
Student (注册,把信息字段都存储起来)
Teacher (注册,把信息字段都存储起来)
Manager (注册,把信息字段都存储起来)

实操:
我现在用反射技术开发一个框架实现:
任何对象只要给我,我就可以把信息和字段都解析并存储起来。

public class ReflectDemo01 {
    public static void main(String[] args) throws Exception {
        Student s1 = new Student(1,"赵敏",26,'女' ,"光明顶","110");
        Mybatis.save(s1);

        Pig peiQi = new Pig("佩奇",500.0 , "粉色","小红","母猪");
        Mybatis.save(peiQi);
    }
}

Mybaits

public class Mybatis {
    // 提供一个方法:可以保存一切对象数据的字段和具体值
    public static void save(Object obj) {
        try(
                PrintStream ps =
                        new PrintStream(new FileOutputStream("Day12Demo/src/datas.txt",true));
          ){
            // 解析对象的字段和每个字段的值存储起来!
            // obj = Student/Pig/Teacher....
            // 1.先得到对象的Class文件对象
            Class c = obj.getClass();
            ps.println("========="+c.getSimpleName()+"===========");
            // 2.定位它的全部成员变量
            Field[] fields = c.getDeclaredFields();
            // 3.遍历这些字段并且取值
            for (Field field : fields) {
                // 字段名称
                String name = field.getName();
                // 字段的值
                field.setAccessible(true); // 暴力反射!
                String value = field.get(obj)+"";
                ps.println(name+"="+value);
            }
        }catch (Exception e){
            e.printStackTrace();
        }

    }
}

Pig类

Student类

posted @ 2023-02-20 20:10  鸽鴿  阅读(29)  评论(0)    收藏  举报