【JAVA基础之反射】反射详解

🔥作者主页小林同学的学习笔录

🔥mysql专栏小林同学的专栏

1.反射

1.1 概述

是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;

对于任意一个对象,都能够调用它的任意属性和方法;

这种动态获取信息以及动态调用对象方法的功能称为Java语言的反射机制。

通过反射可以获取到这些东西

而获取这些信息需要在字节码文件获取

1.2 获取字节码文件对象

获取class对象的三种方式:

  • Class.forName("全类名")
    • 最为常用
  • 类名.class
    • 一般当作参数来使用
  • 对象.getClass()
    • 已经有该类的对象,才可以使用

1.3 字节码文件和字节码文件对象

java文件:就是我们自己编写的java代码。

字节码文件:就是通过java文件编译之后的class文件(是在硬盘上真实存在的,用眼睛能看到的)

字节码文件对象:当class文件加载到内存之后,虚拟机自动创建出来的对象(眼睛看不到的)。

这个对象里面至少包含了:构造方法,成员变量,成员方法。

而我们的反射获取的是什么?字节码文件对象,这个对象在内存中是唯一的。

拓展:

为什么字节码对象是唯一的?

字节码文件对象在内存中是唯一的,是因为每个字节码文件对象都有一个独一无二的标识符来表示它的身份。这个标识符通常是一个地址指针,它指向存储字节码文件对象数据的内存地址。因此,当你创建一个新的字节码文件对象时,系统会为它分配一个新的内存地址,并把这个地址作为该对象的唯一标识符。这样,在内存中就不会出现相同的字节码文件对象,保证了每个字节码文件对象在内存中是唯一的。

1.4 获取构造方法

成员方法:

当使用反射机制时,可以通过Class类的对象调用newInstance()方法来实例化一个类的对象,而不需要直接使用new关键字来创建对象。这种方式在编写通用代码或者动态加载类的情况下非常有用。

代码演示:

java 复制代码
public class Demo01 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        Class aClass = Class.forName("com.lhx.pojo.Student");
        Constructor[] constructors = aClass.getDeclaredConstructors();
//        for (Constructor constructor : constructors) {
//            System.out.println(constructor);
//        }

        Constructor[] constructors1 = aClass.getConstructors();
//        for (Constructor constructor : constructors1) {
//            System.out.println(constructor);
//        }
        
        Constructor constructor = aClass.getConstructor(String.class);
        System.out.println(constructor);

        Constructor constructor1 = aClass.getDeclaredConstructor(String.class, int.class);
        constructor1.setAccessible(true);
        System.out.println(constructor1);
        Object o1 = constructor1.newInstance("周杰伦",18);
        System.out.println(o1);
        
        Constructor constructor2 = aClass.getConstructor();
        Object o =  constructor2.newInstance();
        System.out.println(o);
    }
}

输出结果:

public com.lhx.pojo.Student(java.lang.String)
private com.lhx.pojo.Student(java.lang.String,int)
Student{name = 周杰伦, age = 18}
Student{name = null, age = 0}
java 复制代码
public class Student {
    private String name;

    private int age;


    public Student() {

    }

    public Student(String name) {
        this.name = name;
    }

    private Student(String name, int age) {
        this.name = name;
        this.age = age;
    }


    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}

1.5 获取成员变量

成员方法:

代码演示:

java 复制代码
public class Demo02 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
        Class aClass = Class.forName("com.lhx.pojo.Student02");
        Field[] fields = aClass.getFields();
//        for (Field field : fields) {
//            System.out.println(field);
//        }

        Field[] fields1 = aClass.getDeclaredFields();
//        for (Field field : fields1) {
//            System.out.println(field);
//        }
        
        Field field1 = aClass.getDeclaredField("name");
        System.out.println(field1);
        //private临时修饰他的访问权限(暴力反射),不然下面没办法set()
        field1.setAccessible(true);
        Student02 student02 = new Student02();
        //参数一:表示要修改哪个对象的name?
        //参数二:表示要修改为多少?
        field1.set(student02,"林俊杰");
        String o = (String) field1.get(student02);
        System.out.println(o);

        Field field = aClass.getField("gender");
        System.out.println(field);

        Field age = aClass.getDeclaredField("age");
        System.out.println(age);
    }
}
java 复制代码
public class Student02 {
        private String name;

        private int age;

        public String gender;

        public String address;


        public Student02() {
        }

        public Student02(String name, int age, String address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }


        public Student02(String name, int age, String gender, String address) {
            this.name = name;
            this.age = age;
            this.gender = gender;
            this.address = address;
        }

        /**
         * 获取
         * @return name
         */
        public String getName() {
            return name;
        }

        /**
         * 设置
         * @param name
         */
        public void setName(String name) {
            this.name = name;
        }

        /**
         * 获取
         * @return age
         */
        public int getAge() {
            return age;
        }

        /**
         * 设置
         * @param age
         */
        public void setAge(int age) {
            this.age = age;
        }

        /**
         * 获取
         * @return gender
         */
        public String getGender() {
            return gender;
        }

        /**
         * 设置
         * @param gender
         */
        public void setGender(String gender) {
            this.gender = gender;
        }

        /**
         * 获取
         * @return address
         */
        public String getAddress() {
            return address;
        }

        /**
         * 设置
         * @param address
         */
        public void setAddress(String address) {
            this.address = address;
        }

        public String toString() {
            return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", address = " + address + "}";
        }
}

1.6 获取成员方法

成员方法:

代码演示:

java 复制代码
public class Demo04 {
    public static void main(String[] args) throws ClassNotFoundException {
        Class aClass = Class.forName("com.lhx.pojo.Student");
        //返回public方法,也会把父类(Object)的public的方法也输出
        Method[] methods = aClass.getMethods();
        for (Method method : methods) {
            System.out.println(method);
        }

        //返回所有方法,但是不会返回父类的方法
        Method[] declaredMethods = aClass.getDeclaredMethods();
//        for (Method declaredMethod : declaredMethods) {
//            System.out.println(declaredMethod);
//        }
    }
}

输出结果:

public int com.lhx.pojo.Student.getAge()
public void com.lhx.pojo.Student.setAge(int)
public java.lang.String com.lhx.pojo.Student.getName()
public java.lang.String com.lhx.pojo.Student.toString()
public void com.lhx.pojo.Student.setName(java.lang.String)
public final void java.lang.Object.wait(long,int) throws java.lang.InterruptedException
public final void java.lang.Object.wait() throws java.lang.InterruptedException
public final native void java.lang.Object.wait(long) throws java.lang.InterruptedException
public boolean java.lang.Object.equals(java.lang.Object)
public native int java.lang.Object.hashCode()
public final native java.lang.Class java.lang.Object.getClass()
public final native void java.lang.Object.notify()
public final native void java.lang.Object.notifyAll()
java 复制代码
public class Student {
    private String name;

    private int age;


    public Student() {

    }

    public Student(String name) {
        this.name = name;
    }

    private Student(String name, int age) {
        this.name = name;
        this.age = age;
    }


    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + "}";
    }
}

1.6.1 获取成员方法并运行

成员方法:

Object invoke(Object obj, Object... args) :运行方法

参数一:用obj对象调用该方法

参数二:调用方法的传递的参数(如果没有就不写)

返回值:方法的返回值(如果没有就不写)

代码演示:

java 复制代码
public class Main {
    public static void main(String[] args) throws Exception {
        // 获取Class对象
        Class<?> clazz = ExampleClass.class;
        
        // 获取要调用的方法
        Method method = clazz.getMethod("exampleMethod", int.class, String.class);
        
        // 实例化对象
        ExampleClass instance = new ExampleClass();
        
        // 调用方法
        Object result = method.invoke(instance, 10, "Hello");
        
        System.out.println("Method result: " + result);
    }
}

class ExampleClass {
    public String exampleMethod(int number, String text) {
        return "Number: " + number + ", Text: " + text;
    }
}

1.7 面试题

你觉得反射好不好?好,有两个方向

第一个方向:无视修饰符访问类中的内容(也就是暴力反射)。但是这种操作在开发中一般不用,都是框架底层来用的。

第二个方向:反射可以跟配置文件结合起来使用,动态的创建对象,动态的调用方法。

1.7.1 泛型擦除

集合中的泛型只在java文件中存在,当编译成class文件之后,就没有泛型了。

拓展:

在Java中,泛型是一种类型安全的编程机制,可以在集合中指定要存储的元素类型。当编译Java源代码时,编译器会对泛型进行类型擦除,将泛型相关的信息在生成的class文件中删除,这个过程称为擦除。因此,当Java源代码编译为class文件之后,其中的泛型信息就被擦除了,class文件中只保留了原始类型的信息。

1.7.2 修改字符串的内容

需要你掌握的是字符串不能修改的真正原因。

字符串,在底层是一个byte类型的字节数组,名字叫做value

sql 复制代码
private final byte[] value;

真正不能被修改的原因:final和private

final修饰value表示value记录的地址值不能修改。

private修饰value而且没有对外提供getvalue和setvalue的方法。所以,在外界不能获取或修改value记录的地址值。

如果要强行修改可以用反射

1.7.3 反射和配置文件结合动态获取

需求: 利用反射根据文件中的不同类名和方法名,创建不同的对象并调用方法。

代码演示:

java 复制代码
public class ReflectDemo9 {
    public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
        //1.读取配置文件的信息
        Properties prop = new Properties();
        FileInputStream fis = new FileInputStream("prop.properties");
        prop.load(fis);
        fis.close();
        System.out.println(prop);
        
        //通过key获取值
        String classname = prop.get("classname");
        String methodname = prop.get("methodname");

        //2.获取字节码文件对象
        Class clazz = Class.forName(classname);

        //3.要先创建这个类的对象
        Constructor con = clazz.getDeclaredConstructor();
        con.setAccessible(true);
        Object o = con.newInstance();
        System.out.println(o);

        //4.获取方法的对象
        Method method = clazz.getDeclaredMethod(methodname);
        method.setAccessible(true);

        //5.运行方法
        method.invoke(o);


    }
}

配置文件中的信息:
classname=com.itheima.a02reflectdemo1.Student
methodname=sleep

后续想要其他类或者其他方法来实现,只需要修改配置文件

1.7.4 利用反射保存对象中的信息

java 复制代码
public class MyReflectDemo {
    public static void main(String[] args) throws IllegalAccessException, IOException {
    /*
        对于任意一个对象,都可以把对象所有的字段名和值,保存到文件中去
    */
       Student s = new Student("小A",23,'女',167.5,"睡觉");
       Teacher t = new Teacher("播妞",10000);
       saveObject(s);
    }

    //把对象里面所有的成员变量名和值保存到本地文件中
    public static void saveObject(Object obj) throws IllegalAccessException, IOException {
        //1.获取字节码文件的对象
        Class clazz = obj.getClass();
        //2. 创建IO流
        BufferedWriter bw = new BufferedWriter(new FileWriter("myreflect\\a.txt"));
        //3. 获取所有的成员变量
        Field[] fields = clazz.getDeclaredFields();
        for (Field field : fields) {
            field.setAccessible(true);
            //获取成员变量的名字
            String name = field.getName();
            //获取成员变量的值
            Object value = field.get(obj);
            //写出数据
            bw.write(name + "=" + value);
            bw.newLine();
        }

        bw.close();

    }
}
java 复制代码
public class Student {
    private String name;
    private int age;
    private char gender;
    private double height;
    private String hobby;

    public Student() {
    }

    public Student(String name, int age, char gender, double height, String hobby) {
        this.name = name;
        this.age = age;
        this.gender = gender;
        this.height = height;
        this.hobby = hobby;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return age
     */
    public int getAge() {
        return age;
    }

    /**
     * 设置
     * @param age
     */
    public void setAge(int age) {
        this.age = age;
    }

    /**
     * 获取
     * @return gender
     */
    public char getGender() {
        return gender;
    }

    /**
     * 设置
     * @param gender
     */
    public void setGender(char gender) {
        this.gender = gender;
    }

    /**
     * 获取
     * @return height
     */
    public double getHeight() {
        return height;
    }

    /**
     * 设置
     * @param height
     */
    public void setHeight(double height) {
        this.height = height;
    }

    /**
     * 获取
     * @return hobby
     */
    public String getHobby() {
        return hobby;
    }

    /**
     * 设置
     * @param hobby
     */
    public void setHobby(String hobby) {
        this.hobby = hobby;
    }

    public String toString() {
        return "Student{name = " + name + ", age = " + age + ", gender = " + gender + ", height = " + height + ", hobby = " + hobby + "}";
    }
}
java 复制代码
public class Teacher {
    private String name;
    private double salary;

    public Teacher() {
    }

    public Teacher(String name, double salary) {
        this.name = name;
        this.salary = salary;
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * 获取
     * @return salary
     */
    public double getSalary() {
        return salary;
    }

    /**
     * 设置
     * @param salary
     */
    public void setSalary(double salary) {
        this.salary = salary;
    }

    public String toString() {
        return "Teacher{name = " + name + ", salary = " + salary + "}";
    }
}

2.动态代理

2.1 概述

动态代理是一种设计模式,它允许在运行时创建代理对象来代替原始对象,从而实现对原始对象的控制或增强。在Java中,动态代理通常通过反射机制实现,主要使用了java.lang.reflect.Proxy类。动态代理可以在不修改原始类的情况下,对其方法调用进行增强、拦截或控制。

动态代理通常用于以下场景:

  1. 在原始类的方法执行前后添加日志记录、性能监控等功能。
  2. 实现远程方法调用(如RMI)。
  3. 实现事务管理。
  4. 实现权限控制。

动态代理实现在Java中比较灵活,能够在运行时动态生成代理类,并关联到目标对象上,从而实现代理模式的各种功能。需要注意的是,动态代理基于接口的实现,因此被代理的类必须实现一个或多个接口。

需求:

代理实现

代码实现:

java 复制代码
public class Test {
    public static void main(String[] args) {
    /*
        需求:
            外面的人想要大明星唱一首歌
             1. 获取代理的对象
                代理对象 = ProxyUtil.createProxy(大明星的对象);
             2. 再调用代理的唱歌方法
                代理对象.唱歌的方法("只因你太美");
     */
        //1. 获取代理的对象
        BigStar bigStar = new BigStar("鸡哥");
        Star proxy = ProxyUtil.createProxy(bigStar);

        //2. 调用唱歌的方法
        String result = proxy.sing("只因你太美");
        System.out.println(result);
    }
}
java 复制代码
/*
*
* 类的作用:
*       创建一个代理
*
* */
public class ProxyUtil {
    /*
    *
    * 方法的作用:
    *       给一个明星的对象,创建一个代理
    *
    *  形参:
    *       被代理的明星对象
    *
    *  返回值:
    *       给明星创建的代理
    *
    *
    *
    * 需求:
    *   外面的人想要大明星唱一首歌
    *   1. 获取代理的对象
    *      代理对象 = ProxyUtil.createProxy(大明星的对象);
    *   2. 再调用代理的唱歌方法
    *      代理对象.唱歌的方法("只因你太美");
    * */
    public static Star createProxy(BigStar bigStar){
       /* java.lang.reflect.Proxy类:提供了为对象产生代理对象的方法:

        public static Object newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)
        参数一:用于指定用哪个类加载器,去加载生成的代理类
        参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
        参数三:用来指定生成的代理对象要干什么事情*/
        Star star = (Star) Proxy.newProxyInstance(
                ProxyUtil.class.getClassLoader(),//参数一:用于指定用哪个类加载器,去加载生成的代理类
                new Class[]{Star.class},//参数二:指定接口,这些接口用于指定生成的代理长什么,也就是有哪些方法
                //参数三:用来指定生成的代理对象要干什么事情
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        /*
                        * 参数一:代理的对象
                        * 参数二:要运行的方法 sing
                        * 参数三:调用sing方法时,传递的实参
                        * */
                        if("sing".equals(method.getName())){
                            System.out.println("准备话筒,收钱");
                        }else if("dance".equals(method.getName())){
                            System.out.println("准备场地,收钱");
                        }
                        //去找大明星开始唱歌或者跳舞
                        //代码的表现形式:调用大明星里面唱歌或者跳舞的方法
                        return method.invoke(bigStar,args);
                    }
                }
        );
        return star;
    }
}
java 复制代码
public interface Star {
    //我们可以把所有想要被代理的方法定义在接口当中
    //唱歌
    public abstract String sing(String name);
    //跳舞
    public abstract void dance();
}
java 复制代码
public class BigStar implements Star {
    private String name;


    public BigStar() {
    }

    public BigStar(String name) {
        this.name = name;
    }

    //唱歌
    @Override
    public String sing(String name){
        System.out.println(this.name + "正在唱" + name);
        return "谢谢";
    }

    //跳舞
    @Override
    public void dance(){
        System.out.println(this.name + "正在跳舞");
    }

    /**
     * 获取
     * @return name
     */
    public String getName() {
        return name;
    }

    /**
     * 设置
     * @param name
     */
    public void setName(String name) {
        this.name = name;
    }

    public String toString() {
        return "BigStar{name = " + name + "}";
    }
}
相关推荐
罗曼蒂克在消亡2 分钟前
2.3MyBatis——插件机制
java·mybatis·源码学习
Fairy_sevenseven7 分钟前
【二十八】【QT开发应用】模拟WPS Tab
开发语言·qt·wps
_GR14 分钟前
每日OJ题_牛客_牛牛冲钻五_模拟_C++_Java
java·数据结构·c++·算法·动态规划
蜡笔小新星15 分钟前
Python Kivy库学习路线
开发语言·网络·经验分享·python·学习
凯子坚持 c15 分钟前
C语言复习概要(三)
c语言·开发语言
无限大.27 分钟前
c语言200例 067
java·c语言·开发语言
余炜yw28 分钟前
【Java序列化器】Java 中常用序列化器的探索与实践
java·开发语言
攸攸太上29 分钟前
JMeter学习
java·后端·学习·jmeter·微服务
篝火悟者29 分钟前
问题-python-运行报错-SyntaxError: Non-UTF-8 code starting with ‘\xd5‘ in file 汉字编码问题
开发语言·python
Kenny.志32 分钟前
2、Spring Boot 3.x 集成 Feign
java·spring boot·后端