Java 反射机制

目录

一. 概述

二. 反射的特点

三. 反射主要应用

四. 反射注意事项

五. 代码示例详解

1. 解析图:

2. 获取字节码对象

3. 通过反射获取构造函数并创建对象

4. 通过反射获取对象字段信息

5. 通过反射获取方法并执行

6. 通过反射获取注解信息

六. 总结

1. 获取Class字节码对象方式

2. 利用反射获取构造函数对象

3. 利用反射获取对象字段信息

4. 利用反射获取方法并执行

5. 利用反射获取类、字段、方法、方法参数中的注解


一. 概述

        Java 反射机制是Java语言的一个特性,它允许程序在运行时访问、检查和修改它自己的结构,例如类、接口、字段(成员变量)和方法。反射机制使得Java程序能够在运行时动态地创建对象、调用方法、改变字段值等,而不需要在编译时就知道具体的类信息。

        说人话:就是通过Class字节码文件获取对象信息、成员变量、构造、方法,还可以获取到父类中方法,私有方法等。

二. 反射的特点

  1. 动态性: 反射允许程序在运行时加载、探查、使用编译期间完全未知的.class文件。

  2. 通用性: 反射提供了一种通用的方法来处理对象,不依赖于具体的类。

  3. 扩展性: 反射允许程序动态地扩展,可以处理在编译时未知的类。

  4. 内省: 反射提供了一种方式来获取类和对象的信息,如字段、方法、构造器等。

三. 反射主要应用

  1. 创建灵活的代码: 可以使用反射动态地创建对象和调用方法,使代码更加灵活。

  2. 开发框架: 许多Java框架(如Spring、Hibernate)使用反射来实现依赖注入、代理模式等功能。

  3. 测试工具: JUnit 等测试框架使用反射来动态地调用测试方法。

  4. 动态代理: Java 提供了java.lang.reflect.Proxy类和java.lang.reflect.InvocationHandler接口来实现动态代理。

  5. 访问私有成员: 通过反射可以访问和修改私有字段和方法。

四. 反射注意事项

  1. 性能开销: 反射操作通常比直接的Java代码操作要慢,因为它们需要额外的解析步骤。

  2. 安全限制: 反射可能破坏封装性,因此需要确保代码的安全性。

  3. 异常处理: 反射操作可能会抛出多种异常,如ClassNotFoundExceptionNoSuchMethodExceptionIllegalAccessException等(也可通过反射机制获取方法抛出的异常信息)。

五. 代码示例详解

1. 解析图:

通过Class字节码对象可以获取:

  1. 获取构造方法对象
    1. 获取构造权限修饰符(public,protected,private)
    2. 获取构造函数名称
    3. 获取形参
    4. 创建对象 newInstance()
  2. 获取字段(成员变量)
    1. 获取字段权限修饰符(public,protected,private)
    2. 获取字段名称
    3. 获取参数类型
    4. 设置值/获取值
  3. 获取成员方法
    1. 获取方法修饰符(public,protected,private)
    2. 获取方法名称
    3. 获取形参
    4. 运行方法

2. 获取字节码对象

        通过反射机制获取对象信息需要先获取 Class 字节码对象,获取Class字节码对象可以通过三种方式获取:

  1. 通过全限定名:Class 类中的静态方法 forName(全类名)
  2. 通过类.class:类名称.class
  3. 通过对象中的方法:对象中getClass()方法获取字节码对象
package com.demo.reflect;
/**
 * 文件名:Mamin
 * 创建者:
 * 创建时间:2024-09-09
 * 描述:获取class字节码对象三种方式
 */
public class Main {
    public static void main(String[] args) throws ClassNotFoundException {
        //1. 通过权限定名获取字节码文件(常用方式)
        Class forClass =  Class.forName("com.demo.reflect.User");
        //2. 通过类名获取class字节码对象(多使用在参数中)
        Class userClass = User.class;
        //3. 通过对象获取class字节码对象(通过对象获取字节码对象时使用)
        User user = new User();
        Class obectUserClass = user.getClass();
        //因为class字节码文件在硬盘中是唯一的,所以说三种方法获得的class对象是同一个
        if((forClass == userClass) && (forClass == obectUserClass)){
            System.out.println("同一个对象");
        }
    }
}

3. 通过反射获取构造函数并创建对象

3.1. Class类中用于获取构造方法的方法

//返回所有公共构造方法对象的数组
Constructor<?>[] getConstructors();
//返回所有构造方法对象的数组
Constructor<?>[] getDeclaredConstructors();
//返回单个公共构造方法对象
Constructor<T> getConstructor(Class<?>... parameterTypes);
//返回单个构造方法对象
Constructor<T> getDeclaredConstructor(Class<?>.. parameterTypes)

3.2. Constructor类中用于创建对象的方法

//根据指定的构造方法创建对象
Constructor类中用于创建对象的方法 T newInstance(Object...initargs);
//设置为true,表示取消访问检查(私有修饰符必须设置这个)
setAccessible(boolean flag);

3.3. 代码

  1. 创建User用户类
    package com.demo.reflect;
    
    /**
     * 文件名:User
     * 创建者:
     * 创建时间:2024-09-09
     * 描述:自定义用户信息类
     */
    public class User {
        private String name; //名称
        private int age; //年龄
        public boolean sex; // 性别
    
        public User() {
        }
    
        public User(String name) {
            this.name = name;
        }
    
        protected User(int age) {
            this.age = age;
        }
    
        private User(boolean sex) {
            this.sex = sex;
        }
    
        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 boolean isSex() {
            return sex;
        }
    
        public void setSex(boolean sex) {
            this.sex = sex;
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", sex=" + sex +
                    '}';
        }
    }
    
  2. 创建测试类
    package com.demo.reflect;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Parameter;
    
    /**
     * 文件名:Demo01
     * 创建者:
     * 创建时间:2024-09-09
     * 描述:
     */
    public class Demo01 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
            //1.通过权限定名获取class字节码对象
            Class clazz =  Class.forName("com.demo.reflect.User");
            //2.获取单个 private 修饰的有参构造
            Constructor privateCon = clazz.getDeclaredConstructor(String.class);
            privateCon.setAccessible(true); //如果修饰符为私有的必须设置为true,表示取消访问检查
            System.out.println("获取单个 private 私有构造:"+privateCon);
    
            //3.通过 newInstance() 方法创建User对象,参数是构造里的参数
            User user = (User) privateCon.newInstance("码农");
            System.out.println(user);
    
            //4.获取修饰符 private 返回的是int类型,这个jdk定义的常量,可在jdk文档中查看
            int pri = privateCon.getModifiers();
            System.out.println(pri);
    
            //5.获取构造参数
            Parameter[] pars = privateCon.getParameters();
            for (Parameter par : pars) {
                System.out.println(par);
            }
    
            System.out.println("===================下面都是获取构造对象,其他都一样=============================");
    
            Constructor[] pubCon = clazz.getConstructors();
            for(Constructor con : pubCon){
                System.out.println("获取所有public修饰的构造方法 : "+con);
            }
    
            //获取 public,private,protected 修饰的所有构造
            Constructor[] cons = clazz.getDeclaredConstructors();
            for(Constructor c : cons){
                System.out.println("获取所有构造包括 private 和 protected : "+c);
            }
    
            //获取单个 private 修饰的有参构造
            Constructor privCon = clazz.getDeclaredConstructor(boolean.class);
            System.out.println("获取单个 private 私有构造:"+privCon);
        }
    }
    
  3. 测试结果

4. 通过反射获取对象字段信息

4.1. Class类中获取字段属性信息的方法

//返回所有公共成员变量对象的数组
Field[] getFields();
//返回所有成员变量对象的数组
Field[] getDeclaredFields();
//返回单个公共成员变量对象
Field getField(String name);
返回单个成员变量对象
Field getDeclaredField(String name);

4.2. Field类中创建对象的方法

//赋值
void set(Object obj,Object value);
//获取值
Object get(Object obj);

4.3. 代码

  1. 测试类
    package com.demo.reflect;
    
    
    import java.lang.reflect.Field;
    
    /**
     * 文件名:Demo02
     * 创建者:
     * 创建时间:2024-09-09
     * 描述:通过反射获取成员变量测试类
     */
    public class Demo02 {
        public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
            //1.通过权限定名获取class字节码对象
            Class clazz =  Class.forName("com.demo.reflect.User");
            //2.获取公开的所有属性
            Field[] fields = clazz.getFields();
            for (Field field : fields) {
                System.out.println("获取public修饰的属性:"+field);
            }
            //3.获取所有的的属性,包括私有的属性
            Field[] fields1 = clazz.getDeclaredFields();
            for (Field field : fields1) {
                System.out.println("获取public、private、protected修饰的属性:"+field);
            }
            //4.获取单个属性私有修饰也可获取
            Field name =  clazz.getDeclaredField("name");
            name.setAccessible(true); //如果修饰符为私有的必须设置为true,表示取消访问检查
            System.out.println("获取单个属性:"+name);
            //5.获取属性名称
            String cName = name.getName();
            System.out.println("获取属性名称:"+cName);
            //6.获取属性修饰符
            int pr = name.getModifiers();
            System.out.println("获取修饰符:"+pr);
            //7.获取成员变量的类型
            Class cType = name.getType();
            System.out.println("获取成员变量的类型:"+cType);
            //8.获取属性的值
            User user = new User();
            user.setName("测试");
            Object userName = name.get(user);
            System.out.println("获取对象的值:"+userName);
    
            //9.通过反射修改对象的值
            name.set(user,"张三"); // 如果字段修饰符是私有修饰符需要设置name.setAccessible(true),取消访问检查
            System.out.println("修改对象值:"+user);
        }
    }
    
  2. 测试结果

5. 通过反射获取方法并执行

5.1. 利用Class字节码对象通过反射获取成员方法对象

//返回所有公共成员方法对象的数组,包括继承的
Method[] getMethods();
//返回所有成员方法对象的数组,不包括继承的
Method[] getDeclaredMethods();
//返回单个公共成员方法对象
Method getMethod(String name, Class<?>.. parameterTypes);
//返回单个成员方法对象
Method getDeclaredMethod(String name, Class<?>..parameterTypes);

5.2. 创建方法对象并执行方法

//Method类中用于创建对象的方法
//参数一:用obj对象调用该方法
//参数二:调用方法的传递的参数(如果没有就不写)
//返回值:方法的返回值(如果没有就不写)
Object invoke(Object obj, Object... args);//运行方法

5.3. 代码

  1. 在User类中添加新成员方法
    package com.demo.reflect;
    
    import java.io.IOException;
    
    /**
     * 文件名:User
     * 创建者:
     * 创建时间:2024-09-09
     * 描述:自定义用户信息类
     */
    public class User {
        private String name; //名称
        private int age; //年龄
        public boolean sex; // 性别
        public User() {}
        public User(String name) {
            this.name = name;
        }
        protected User(int age) {
            this.age = age;
        }
        private User(boolean sex) {
            this.sex = sex;
        }
        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 boolean isSex() {
            return sex;
        }
        public void setSex(boolean sex) {
            this.sex = sex;
        }
    
        private String eat(String name)throws IOException {
            return name+"在吃饭";
        }
        private void game(String name){
            System.out.println(name+"在打游戏");
        }
    
        public String work(String name){
            System.out.println(name+"在工作");
            return "小明工作很忙";
        }
    
        @Override
        public String toString() {
            return "User{" +
                    "name='" + name + '\'' +
                    ", age=" + age +
                    ", sex=" + sex +
                    '}';
        }
    }
    
  2. 测试类型
    package com.demo.reflect;
    
    import java.lang.reflect.InvocationTargetException;
    import java.lang.reflect.Method;
    import java.lang.reflect.Parameter;
    
    /**
     * 文件名:Demo03
     * 创建者:
     * 创建时间:2024-09-10
     * 描述:通过反射获取对象成员方法测试类
     */
    public class Demo03 {
    
        public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException {
            //1.通过权限定名获取class字节码对象
            Class clazz =  Class.forName("com.demo.reflect.User");
    
            //2.获取对象方法信息(这个返回的方法会包含父类公开的方法)
            Method[] methods = clazz.getMethods();
            for (Method method : methods) {
                System.out.println(method);
            }
    
            //3.获取当前对象的方法包括私有方法(这个不会返回父类的方法)
            Method[] methods1= clazz.getDeclaredMethods();
            for (Method method : methods1) {
                System.out.println(method);
            }
    
            //4.获取指定单个私有的方法
            Method method = clazz.getDeclaredMethod("eat", String.class);
            System.out.println("获取指定单个私有的方法:"+method);
            System.out.println("方法修饰符:"+method.getModifiers());//获取方法的修饰符
            System.out.println("方法名字:"+method.getName());//获取方法的名字
            System.out.println("参数个数:"+method.getParameterCount());//获取参数个数
            Parameter[] parameters = method.getParameters(); //获取方法的参数
            for (Parameter parameter : parameters) {
                System.out.println("方法参数:"+parameter);
            }
            Class[] ex = method.getExceptionTypes(); //获取方法的抛出的异常
            for (Class aClass : ex) {
                System.out.println("异常信息"+aClass);
            }
    
            //5.获取指定单个方法(包括私有)并运行某个方法
            Method met = clazz.getDeclaredMethod("game",String.class);
            met.setAccessible(true);如果修饰符为私有的必须设置为true,表示取消访问检查
            //运行方法
            //参数一:用obj对象调用该方法
            //参数二:调用方法的传递的参数(如果没有就不写)
            //返回值:方法的返回值(如果没有就不写)
            User user = new User();
            met.invoke(user,"小明");
    
            //6.获取公开的单个有参方法并运行,打印返回值
            Method method1 = clazz.getMethod("work", String.class);
            Object res = method1.invoke(user,"小明");
            System.out.println("返回值:"+res);
    
        }
    }
    
  3. 测试结果

6. 通过反射获取注解信息

6.1. 在java中可以通过反射的方式获取类、字段、方法、方法参数上的注解,java中的注解有广发的应用,特别是在框架开发上,基于注解的实现框架多不胜数。

注解获取主要从:

  1. 获取java类注解
  2. 获取对象属性上注解
  3. 获取方法上注解
  4. 获取方法参数注解

6.2. 代码实现

  1. 自定义注解(类注解、字段注解、方法注解、方法参数注解)
     
    package com.demo.reflect;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    /**
     * 文件名:MyAnnotation
     * 创建者:
     * 创建时间:2024-09-10
     * 描述: 自己定义一个类上使用的注解
     */
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotationCalss {
        String value();
    }
    
    package com.demo.reflect;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    /**
     * 文件名:MyAnnotationField
     * 创建者:
     * 创建时间:2024-09-10
     * 描述:自定义一个字段上使用的注解
     */
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotationField {
        String value();
    }
    
    package com.demo.reflect;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    /**
     * 文件名:MyAnnotationMethod
     * 创建者:
     * 创建时间:2024-09-10
     * 描述:  自定义一个方法上使用的注解
     */
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyAnnotationMethod {
        String value();
    }
    
    package com.demo.reflect;
    
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    
    /**
     * 文件名:MyAnnotationCalssParameter
     * 创建者:
     * 创建时间:2024-09-10
     * 描述:自定义一个方法参数上使用的注解
     */
    @Retention(RetentionPolicy.RUNTIME)
    @interface MyAnnotationParameter {
        String value();
    }
    
  2. 定义普通 java 类
    package com.demo.reflect;
    
    /**
     * 文件名:MyClass
     * 创建者:
     * 创建时间:2024-09-10
     * 描述:自定义类,在类上使用自己定义的 @MyAnnotation
     */
    @MyAnnotationCalss(value = "类注解")
    public class MyClass {
    
        @MyAnnotationField(value = "Field-name")
        private String name;
        @MyAnnotationField(value = "Field-age")
        private int age;
    
        @MyAnnotationMethod(value = "Method-game")
        private void game(String name){
            System.out.println(name+"在打游戏");
        }
    
        @MyAnnotationMethod(value = "Method-work")
        public String work(@MyAnnotationParameter(value = "Parameter-name") String name){
            System.out.println(name+"在工作");
            return "小明工作很忙";
        }
    
        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;
        }
    }
    
  3. 定义测试类
    package com.demo.reflect;
    
    import java.lang.annotation.Annotation;
    import java.lang.reflect.Field;
    import java.lang.reflect.Method;
    
    /**
     * 文件名:AnnotationTest
     * 创建者:
     * 创建时间:2024-09-10
     * 描述:通过反射获取 类 属性 方法 方法中参数 的注解测试类
     */
    public class AnnotationTest {
        public static void main(String[] args) throws NoSuchMethodException, NoSuchFieldException {
            Class<MyClass> clazz = MyClass.class;
    
            //1.获取类上带有 @MyAnnotationCalss 注解的实例(这个获取的是多个注解)
            Annotation[] myAnnotations = clazz.getAnnotations();
            for (Annotation annotation : myAnnotations) {
                //多个注解需要判断注解类型
                if(annotation instanceof MyAnnotationCalss){
                    MyAnnotationCalss myAnnotat =((MyAnnotationCalss) annotation);
                    System.out.println("获取类上注解的值:"+myAnnotat.value());
                }
            }
    
            //2.获取类上带有 @MyAnnotationCalss 注解的实例(单个获取,需要传入注解class对象)
            MyAnnotationCalss myAnnotation = clazz.getAnnotation(MyAnnotationCalss.class);
            System.out.println("获取类上注解的值:"+myAnnotation.value());
    
            //3.获取方法上带有 @MyAnnotationMethod 注解的实例
            Method method = clazz.getDeclaredMethod("work",String.class);
            MyAnnotationMethod annotation = method.getAnnotation(MyAnnotationMethod.class);
            System.out.println("获取方法上注解的值:"+annotation.value());
    
            //4.获取方法参数带有 @MyAnnotationParameter 注解的实例(这个返回的是一个二维数组)
            Annotation[][] annotations = method.getParameterAnnotations();
            for (Annotation[] annotation1 : annotations) {
                for (Annotation annotation2 : annotation1) {
                    MyAnnotationParameter myAnnotationParameter = (MyAnnotationParameter) annotation2;
                    System.out.println("获取参数注解的值:"+myAnnotationParameter.value());
                }
            }
    
            //4.获取字段上带有 MyAnnotationField 注解的实例 (获取多个注解)
            Field field = clazz.getDeclaredField("name");
            Annotation[] annots = field.getAnnotations();
            for (Annotation annot : annots) {
                if(annot instanceof MyAnnotationField){
                    System.out.println("字段上的注解:"+annot);
                }
            }
    
            //5.获取字段上带有 MyAnnotationField 注解的实例(获取单个注解)
            MyAnnotationField myAnnotationField = field.getAnnotation(MyAnnotationField.class);
            System.out.println("获取属性注解的值:"+myAnnotationField.value());
    
        }
    }
    
  4. 测试结果

六. 总结

1. 获取Class字节码对象方式

//1. 通过权限定名获取字节码文件(常用方式)
Class forClass =  Class.forName("com.demo.reflect.User");
//2. 通过类名获取class字节码对象(多使用在参数中)
Class userClass = User.class;
//3. 通过对象获取class字节码对象(通过对象获取字节码对象时使用)
User user = new User();
Class obectUserClass = user.getClass();

2. 利用反射获取构造函数对象

//1.返回所有公共构造方法对象的数组
Constructor<?>[] getConstructors();
//2.返回所有构造方法对象的数组
Constructor<?>[] getDeclaredConstructors();
//3.返回单个公共构造方法对象
Constructor<T> getConstructor(Class<?>... parameterTypes);
//4.返回单个构造方法对象
Constructor<T> getDeclaredConstructor(Class<?>.. parameterTypes)


//Constructor类中用于创建对象的方法
//根据指定的构造方法创建对象
T newInstance(Object...initargs);
//设置为true,表示取消访问检查(私有修饰符必须设置这个)
setAccessible(boolean flag);

3. 利用反射获取对象字段信息

//1.返回所有公共成员变量对象的数组
Field[] getFields();
//2.返回所有成员变量对象的数组
Field[] getDeclaredFields();
//3.返回单个公共成员变量对象
Field getField(String name);
//4.返回单个成员变量对象
Field getDeclaredField(String name);


//赋值
void set(Object obj,Object value);
//获取值
Object get(Object obj);


4. 利用反射获取方法并执行

//1.返回所有公共成员方法对象的数组,包括继承的
Method[] getMethods();
//2.返回所有成员方法对象的数组,不包括继承的
Method[] getDeclaredMethods();
//3.返回单个公共成员方法对象
Method getMethod(String name, Class<?>.. parameterTypes);
//4.返回单个成员方法对象
Method getDeclaredMethod(String name, Class<?>..parameterTypes);

//Method类中用于创建对象的方法
//参数一:用obj对象调用该方法
//参数二:调用方法的传递的参数(如果没有就不写)
//返回值:方法的返回值(如果没有就不写)
Object invoke(Object obj, Object... args);//运行方法

5. 利用反射获取类、字段、方法、方法参数中的注解

//1.获取类上多个注解(返回一个数组)
Annotation[] myAnnotations = clazz.getAnnotations();
//2.获取类上单个注解
Annotation annotation = clazz.getAnnotation(MyAnnotationCalss.class);
//3.获取方法上的注解
MyAnnotationMethod annotation = method.getAnnotation(MyAnnotationMethod.class);
//4.获取方法参数上的注解(返回的是二维数组)
Annotation[][] annotations = method.getParameterAnnotations();
//5.获取字段上的注解(返回多个注解)
Annotation[] annots = field.getAnnotations();
//6.获取字段上的注解(返回单个注解)
MyAnnotationField myAnnotationField = field.getAnnotation(MyAnnotationField.class);
  • 33
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

动物园首领

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值