【Java】Java 的反射机制(三):反射的实际运用

Java 的反射机制》系列,共包含以下文章:

😊 如果您觉得这篇文章有用 ✔️ 的话,请给博主一个一键三连 🚀🚀🚀 吧 (点赞 🧡、关注 💛、收藏 💚)!!!您的支持 💖💖💖 将激励 🔥 博主输出更多优质内容!!!

Java 的反射机制(三):反射的实际运用

1.获取类的运行时结构

通过反射获取运行时类的完整结构:FieldMethodConstructorSuperclassInterfaceAnnotation

  • 实现的全部 接口
  • 所继承的 父类
  • 全部的 构造器
  • 全部的 方法
  • 全部的 Field
  • 注解
java 复制代码
package com.pp.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;

// 获得类的信息
public class Test07 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, NoSuchFieldException {
        Class c1 = Class.forName("com.pp.reflection.User");

//        User user = new User();
//        c1 = user.getClass();

        // 获得类的名字
        System.out.println("==============获得类的名字");
        System.out.println(c1.getName()); // 获得包名 + 类名
        System.out.println(c1.getTypeName()); // 获得类名

        // 获得类的属性
        System.out.println("\n==============获得类的属性");
        Field[] fields = c1.getFields(); // 只能找到public属性
//        for (Field field : fields) {
//            System.out.println(field);
//        }

        fields = c1.getDeclaredFields(); // 找到全部的属性
        for (Field field : fields) {
            System.out.println(field);
        }

        // 获得指定属性的值
        System.out.println("\n==============获得指定属性的值");
        Field name = c1.getDeclaredField("name");
        System.out.println(name);

        // 获得类的方法
        System.out.println("\n==============获得类的方法");

        Method[] methods = c1.getMethods(); // 获得本类及其父类的全部public方法
        for (Method method : methods) {
            System.out.println("public方法: "+method);
        }
        methods = c1.getDeclaredMethods(); // 获得本类的所有方法(包括私有方法)
        for (Method declaredMethod : methods) {
            System.out.println("所有方法: "+declaredMethod);
        }

        // 获得指定方法
        System.out.println("\n==============获得指定方法");
        Method getName = c1.getMethod("getName", null);
        Method setName = c1.getMethod("setName", String.class);
        System.out.println(getName);
        System.out.println(setName);

        // 获得指定的构造器
        System.out.println("\n==============获得指定的构造器");
        Constructor[] constructors = c1.getConstructors(); // 获得public构造方法
        for (Constructor constructor : constructors) {
            System.out.println("public构造方法: "+constructor);
        }
        Constructor[] declaredConstructors = c1.getDeclaredConstructors(); // 获得所有构造方法
        for (Constructor declaredConstructor : declaredConstructors) {
            System.out.println("所有构造方法: "+declaredConstructor);
        }

        // 获得指定的构造器
        Constructor constructor = c1.getConstructor(String.class, int.class, int.class);
        System.out.println("指定构造方法: "+constructor);
    }
}


  • 在实际的操作中,取得类的信息的操作代码,并不会经常开发。
  • 一定要熟悉 java.lang.reflect 包的作用,反射机制。
  • 如何取得属性、方法、构造器的名称,修饰符等。

2.动态创建对象执行方法

🚀 有了 Class 对象,能做什么?

2.1 创建类的对象

调用 Class 对象的 newInstance() 方法。

  • 类必须有一个无参数的构造器。
  • 类的构造器的访问权限需要足够。

🚀 思考:难道没有无参的构造器就不能创建对象了吗?只要在操作的时候明确的调用类中的构造器,并将参数传递进去之后,就可以实例化操作。

步骤如下:

  • 通过 Class 类的 getDeclaredConstructor(Class ... parameterTypes) 取得本类的指定形参类型的构造器。
  • 向构造器的形参中传递一个对象数组进去,里面包含了构造器中所需的各个参数。
  • 通过 Constructor 实例化对象。

2.2 调用指定方法

通过反射,调用类中的方法,通过 Method 类完成。

  • 通过 Class 类的 getMethod(String name,Class...parameterTypes) 方法取得一个 Method 对象,并设置此方法操作时所需要的参数类型。
  • 之后使用 Object invoke(Object obj, Object[] args) 进行调用,并向方法中传递要设置的 obj 对象的参数信息。

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

  • Object 对应原方法的返回值,若原方法无返回值,此时返回 null
  • 若原方法若为静态方法,此时形参 Object obj 可为 null
  • 若原方法形参列表为空,则 Object[] argsnull
  • 若原方法声明为 private,则需要在调用此 invoke() 方法前,显式调用方法对象的 setAccessible(true) 方法,将可访问 private 的方法。

2.4 setAccessible

  • Method 和 Field、Constructor 对象都有 setAccessible() 方法。
  • setAccessible 作用是启动和禁用访问安全检查的开关。
  • 参数值为 true,则指示反射的对象在使用时应该取消 Java 语言访问检查。
    • 提高反射的效率。如果代码中必须用反射,而该句代码需要频繁的被调用,那么请设置为 true
    • 使得原本无法访问的私有成员也可以访问。
  • 参数值为 false,则指示反射的对象应该实施 Java 语言访问检查。

2.5 代码示例

java 复制代码
package com.pp.reflection;

import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

// 动态的创建对象,通过反射
public class Test08 {
    public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
        // 获得Class对象
        Class c1 = Class.forName("com.pp.reflection.User");

        // 构造对象
        User user = (User)c1.newInstance(); // 本质是调用了类的无参构造器
        System.out.println(user);

        // 通过构造器创建对象
        Constructor constructor = c1.getDeclaredConstructor(String.class, int.class, int.class);
        User user2 = (User)constructor.newInstance("猫猫", 001, 18);
        System.out.println(user2);

        // 通过反射调用普通方法
        User user3 = (User)c1.newInstance();

        // 通过反射获取一个方法
        Method setName = c1.getDeclaredMethod("setName", String.class);

        // invoke:激活的意思
        // (对象,"方法的值")
        setName.invoke(user3,"狗子");
        System.out.println(user3.getName());

        // 通过反射操作属性
        User user4 = (User)c1.newInstance();
        Field name = c1.getDeclaredField("name");
        // 不能直接操作私有属性, 我们需要关闭程序的安全检测, 属性或者方法的setAccessible(true)
        name.setAccessible(true);
        name.set(user4,"鼠鼠");
        System.out.println(user4.getName());
    }
}

3.性能对比分析

java 复制代码
package com.pp.reflection;

import java.lang.reflect.Method;

// 分析性能问题
public class Test09 {
    // 普通方式调用
    public static void test01(){
        User user = new User();
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            user.getName();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("普通方式执行10亿次: "+(endTime-startTime)+"ms");
    }

    // 反射方式调用
    public static void test02() throws Exception {
        User user = new User();
        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("反射方式执行10亿次: "+(endTime-startTime)+"ms");
    }

    // 反射方式调用, 关闭检测
    public static void test03() throws Exception {
        User user = new User();
        Class c1 = user.getClass();
        Method getName = c1.getDeclaredMethod("getName", null);
        getName.setAccessible(true);
        long startTime = System.currentTimeMillis();
        for (int i = 0; i < 1000000000; i++) {
            getName.invoke(user,null);
        }
        long endTime = System.currentTimeMillis();
        System.out.println("关闭检测执行10亿次: "+(endTime-startTime)+"ms");
    }

    public static void main(String[] args) throws Exception {
        test01();
        test02();
        test03();
    }
}

4.反射操作泛型

  • Java 采用泛型擦除的机制来引入泛型,Java 中的泛型仅仅是给编译器 javac 使用的,确保数据的安全性和免去强制类型转换问题。但是,一旦编译完成,所有和泛型有关的类型全部擦除。
  • 为了通过反射操作这些类型,Java 新增了 ParameterizedTypeGenericArrayTypeTypeVariableWildcardType 几种类型来代表不能被归一到 Class 类中的类型但是又和原始类型齐名的类型。
    • ParameterizedType:表示一种参数化类型,比如 Collection<String>
    • GenericArrayType:表示一种元素类型是参数化类型或者类型变量的数组类型。
    • TypeVariable:是各种类型变量的公共父接口。
    • WildcardType:代表一种通配符类型表达式。
java 复制代码
package com.pp.reflection;

import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.List;
import java.util.Map;

// 通过反射获取泛型
public class Test10 {
    public void test01(Map<String, User> map, List<User> list) {
        System.out.println("test01");
    }

    public Map<String, User> test02(){
        System.out.println("test02");
        return null;
    }

    public static void main(String[] args) throws NoSuchMethodException {
        Method method = Test10.class.getMethod("test01", Map.class, List.class);

        Type[] genericParameterTypes = method.getGenericParameterTypes();

        for (Type genericParameterType : genericParameterTypes) {
            System.out.println("======== " + genericParameterType);
            if (genericParameterType instanceof ParameterizedType){
                Type[] actualTypeArguments = ((ParameterizedType) genericParameterType).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.println(actualTypeArgument);
                }
            }
        }

        method = Test10.class.getMethod("test02",null);

        Type genericReturnType = method.getGenericReturnType();

        if (genericReturnType instanceof ParameterizedType) {
            System.out.println("\n======== " + genericReturnType);
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.println(actualTypeArgument);
            }
        }
    }
}

5.反射操作注解

  • getAnnotations
  • getAnnotation

示例:利用注解和反射完成类和表结构的映射关系。

java 复制代码
package com.pp.reflection;

import java.lang.annotation.*;
import java.lang.reflect.Field;

// 练习反射操作注解
public class Test11 {
    public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException {
        Class c1 = Class.forName("com.pp.reflection.Student2");

        // 通过反射获得注解
        System.out.println("========== 通过反射获得注解");
        Annotation[] annotations = c1.getAnnotations();
        for (Annotation annotation : annotations) {
            System.out.println(annotation);
        }

        // 取得注解的 value 的值
        System.out.println("\n========== 取得注解的 value 的值");
        Tablepp tablepp = (Tablepp) c1.getAnnotation(Tablepp.class);
        String value = tablepp.value();
        System.out.println(value);

        // 获得类指定的注解
        System.out.println("\n========== 获得类指定的注解");
        Field f = c1.getDeclaredField("id");
        Field1 annotation = f.getAnnotation(Field1.class);
        System.out.println(annotation.columnName());
        System.out.println(annotation.type());
        System.out.println(annotation.length());

    }
}

@Tablepp("db_student")
class Student2 {
    @Field1(columnName = "db_id", type = "int", length = 10)
    private int id;
    @Field1(columnName = "db_age", type = "int", length = 10)
    private int age;
    @Field1(columnName = "db_name", type = "varchar", length = 3)
    private String name;
    public Student2() {
    }
    public Student2(int id, int age, String name) {
        this.id = id;
        this.age = age;
        this.name = name;
    }

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

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

// 类名的注解
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Tablepp {
    String value();
}

// 属性的注解
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface  Field1 {
    String columnName();
    String type();
    int length();
}
  • @Target:用于描述注解的使用范围(即被描述的注解可以用在什么地方)。
  • @Retention:表示需要在什么级别保存该注释信息,用于描述注解的生命周期。
    • SOURCE < CLASS < RUNTIME
相关推荐
坐吃山猪4 小时前
SpringBoot01-配置文件
java·开发语言
我叫汪枫5 小时前
《Java餐厅的待客之道:BIO, NIO, AIO三种服务模式的进化》
java·开发语言·nio
yaoxtao5 小时前
java.nio.file.InvalidPathException异常
java·linux·ubuntu
Swift社区6 小时前
从 JDK 1.8 切换到 JDK 21 时遇到 NoProviderFoundException 该如何解决?
java·开发语言
DKPT7 小时前
JVM中如何调优新生代和老生代?
java·jvm·笔记·学习·spring
phltxy7 小时前
JVM——Java虚拟机学习
java·jvm·学习
seabirdssss9 小时前
使用Spring Boot DevTools快速重启功能
java·spring boot·后端
喂完待续9 小时前
【序列晋升】29 Spring Cloud Task 微服务架构下的轻量级任务调度框架
java·spring·spring cloud·云原生·架构·big data·序列晋升
benben0449 小时前
ReAct模式解读
java·ai