00-JAVA基础-反射机制

反射

什么是反射

在Java中,反射(Reflection)是Java语言的一个特性,它允许程序在运行时检查类、接口、字段和方法的信息。通过反射,Java代码能够动态地创建对象、调用方法、改变字段的值等,而无需在编译时知道这些类的详细信息。

Java反射主要提供了以下功能:

  1. 获取类的信息:通过反射,你可以获取一个类的Class对象,进而获取该类的名称、父类、实现的接口、声明的字段和方法等信息。
  2. 创建对象:使用反射,你可以在运行时动态地创建类的对象实例,而无需使用new关键字。
  3. 调用方法:通过反射,你可以获取类的方法信息,并动态地调用这些方法,包括私有方法。
  4. 访问和修改字段:反射允许你获取类的字段信息,并可以动态地读取和修改这些字段的值,包括私有字段。
  5. 注解处理:反射也常用于处理Java注解,读取注解信息并执行相应的逻辑。

反射的主要用途包括:

  1. 框架设计:许多Java框架(如Spring、Hibernate等)都大量使用了反射,以实现灵活的配置和扩展。
  2. 测试:在单元测试中,反射经常用于创建和操作测试对象。
  3. 插件机制:在需要支持插件的系统中,反射可以帮助系统在运行时加载和调用插件。

然而,反射也有一些缺点和注意事项:

  1. 性能开销:反射操作通常比直接操作慢,因为涉及了更多的动态解析和类型转换。
  2. 安全问题:通过反射,可以访问和修改类的私有成员,这可能导致安全问题。
  3. 代码可读性:过度使用反射可能导致代码难以理解和维护。

Class对象

Class对象是Java反射机制的核心,它封装了类的元数据(包含了关于类的结构信息,如类的名称、父类、实现的接口、字段、方法、注解等),并提供了在运行时检查和操作这些信息的能力。

每个类在JVM中都有且只有一个与之对应的Class对象,这个对象在类被加载到JVM时由类加载器创建。

案例
UserDo.java

package demo1;

/**
 * 定义UserDo用于测试反射
 *
 * @author Anna.
 * @date 2024/4/3 23:06
 */
public class UserDo {
    private String name;
    private Integer age;
    public String sex;

    public UserDo() {
    }

    public UserDo(String name, Integer age, String sex) {
        this.name = name;
        this.age = age;
        this.sex = sex;
    }

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    /**
     * 定义一个私有方法
     *
     * @param content
     * @return void
     * @author Anna.
     * @date 2024/4/3 23:09
     */
    private void sayHello(String content){System.out.printf("%s说%s%n",this.name,content);}

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

测试代码:

package demo1;

/**
 * 测试反射
 * 获取Class的三种方式
 *
 * @author Anna.
 * @date 2024/4/3 23:10
 */
public class ReflexDemo1 {
    public static void main(String[] args) throws ClassNotFoundException {
        // 通过.class获取
        Class<UserDo> userDoClass1 = UserDo.class;
        System.out.println(userDoClass1);   // class demo1.UserDo

        // 通过UserDo实例调用getClass()方法获取
        Class<? extends UserDo> userDoClass2 = new UserDo().getClass();
        System.out.println(userDoClass2 == userDoClass1); // true

        // 通过Class.forName()获取
        Class<UserDo> userDoClass3 = (Class<UserDo>) Class.forName("demo1.UserDo");
        System.out.println(userDoClass3 == userDoClass2);   // true
    }
}

执行结果

在这里插入图片描述

获取Class对象

  1. 使用.class语法:Class<?> clazz = String.class;
  2. 使用对象的getClass()方法:String s = “Hello”; Class<?> clazz = s.getClass();
  3. 使用Class.forName(包路径.类名)方法(需处理ClassNotFoundException):Class<?> clazz = Class.forName(“java.lang.String”);

Class对象的作用

  1. 类实例化:使用newInstance()方法或反射API创建类的新实例(从Java 9开始,newInstance()方法已被弃用,应使用getDeclaredConstructor().newInstance())。
  2. 反射操作:通过Class对象可以获取类的字段、方法、注解等信息,并可以动态地调用方法、访问和修改字段。
  3. 类型检查与转换:Class对象可以用于运行时类型检查(instanceof)和类型转换(cast)。
  4. 注解处理:通过Class对象可以读取类、方法、字段上的注解信息

泛型与Class对象

泛型在Java中引入了类型参数的概念,但在运行时,由于类型擦除(Type Erasure),泛型类型信息并不完全保留。因此,使用反射处理泛型时可能会遇到一些限制。不过,可以通过一些技巧(如使用类型令牌)来在运行时保留部分泛型类型信息。(通过反射处理注解逻辑时详述)

安全性与性能

使用反射进行类操作时需要注意安全性和性能问题。反射可以绕过编译时的类型检查,从而可能导致运行时错误或安全漏洞。此外,反射操作通常比直接操作慢,因为它们涉及更多的动态解析和类型转换。

反射常用API

类型接口描述
java.lang.Class类-反射的核心类,提供了获取类的元数据信息的方法。
-getClass()调用类或接口实例的方法以获取其Class对象。
-.class语法直接通过类名获取其Class对象。
-Class.forName()通过类的全限定名获取其Class对象。
-getMethods()获取类的所有公共方法。
-getDeclaredMethods()获取类声明的所有方法,包括私有方法
-getConstructors()获取类的所有公共构造方法
-getDeclaredConstructors()获取类声明的所有构造方法,包括私有构造方法
-getFields()获取类的所有公共字段
-getDeclaredFields()获取类声明的所有字段,包括私有字段
-getAnnotations()获取类上的所有注解
-isAnnotationPresent()检查类上是否存在某个特定的注解。
java.lang.reflect.Method类-用于表示类的方法。
-invoke(Object obj, Object… args)调用方法,第一个参数是方法所属的对象,后面的参数是方法的参数。调用私有方法(需要先设置为可访问,通过设置method.setAccessible(true);实现)
-getParameterTypes()获取方法的参数类型数组。
-getReturnType()获取方法的返回类型。
java.lang.reflect.Field类-用于表示类的字段。访问私有字段需要设置为可访问(通过设置field.setAccessible(true);实现)
-get(Object obj)获取字段的值,第一个参数是字段所属的对象。
-set(Object obj)设置字段的值,第一个参数是字段所属的对象。
-getType()获取字段的类型。
java.lang.reflect.Constructor类-用于表示类的构造方法。
-newInstance(Object… initargs)使用此Constructor对象表示的构造方法来创建该构造方法的声明类的新实例。从Java 9开始,newInstance()方法已被弃用,应使用getDeclaredConstructor().newInstance()
java.lang.reflect.Modifier类-用于获取和操作修饰符的工具类。
-isPublic(int modifiers)检查修饰符是否表示类、方法或字段是公共的。
-isPrivate(int modifiers)判断修饰符是否是private。

注意:

setAccessible(true) 可以用来访问私有字段和方法,但这样会破坏封装性,并且可能带来安全风险。

反射操作通常比直接访问慢,因为它们涉及到更多的动态解析和类型转换。

使用反射时应谨慎处理异常,特别是ClassNotFoundException、NoSuchMethodException、IllegalAccessException和InvocationTargetException等。

反射API提供了非常强大的功能,但使用时也需要考虑到其潜在的安全和性能影响。在实际应用中,应仔细权衡反射带来的灵活性和可能带来的问题。

反射案例

package demo2;

import demo1.UserDo;

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

/**
 * 测试反射
 * 1 通过反射创建UserDo对象,
 * 2 调用默认构造器创建对象
 * 3 调用公有方法设置name
 * 4 调用公有方法获取私有字段name属性值
 * 5 调用私有方法sayHello
 *
 * @author Anna.
 * @date 2024/4/3 23:19
 */
public class ReflexDemo {
    public static void main(String[] args) throws Exception {
        // 通过Class.forName()获取Class
        Class<?> clazz = Class.forName("demo1.UserDo");
        // 获取构造方法
        Constructor<?> declaredConstructor = clazz.getDeclaredConstructor(String.class, Integer.class, String.class);
        // 调用newInstance()初始化UserDo
        UserDo userDo = (UserDo) declaredConstructor.newInstance("张三", 20, "男");
        System.out.printf("输出初始化后的UserDo对象:%s%n", userDo);

        // 调用调用公有方法设置name
        Method setNameMethod = clazz.getDeclaredMethod("setName", String.class);
        // 方法调用前需要先初始化 否则会抛出 Exception in thread "main" java.lang.IllegalArgumentException: object is not an instance of declaring class
        // 从Java 9开始,newInstance()方法已被弃用,应使用getDeclaredConstructor().newInstance()
        UserDo userDo1 = (UserDo) clazz.getDeclaredConstructor().newInstance();
        // 执行共育setNameMethod方法
        setNameMethod.invoke(userDo1, "李四");

         调用公有方法获取私有字段name属性值
        Method getNameMethod = clazz.getMethod("getName");
        System.out.printf("通过反射调用getName:%s%n", getNameMethod.invoke(userDo1));

        // 因为已经拿到UserDo的实例也可以通过实例调用getName
        System.out.printf("通过实例调用getName:%s%n", userDo1.getName());

        // 通过反射调用私有方法sayHello
        Method sayHelloMethod = clazz.getDeclaredMethod("sayHello", String.class);
        // 调用私有方法需要设置为可访问 否则会抛出异常 Exception in thread "main" java.lang.IllegalAccessException: class demo2.ReflexDemo cannot access a member of class demo1.UserDo with modifiers "private
        sayHelloMethod.setAccessible(true);
        sayHelloMethod.invoke(userDo1, "通过反射调用私有方法sayHello说了你好啊");

    }
}

执行结果

在这里插入图片描述

比较反射与对象实例调用方法对比

package demo3;

import demo1.UserDo;

import java.lang.reflect.Method;

/**
 * 比较反射与对象实例调用方法对比
 *
 * @author Anna.
 * @date 2024/4/3 23:54
 */
public class ReflexDemo {
    public static void main(String[] args) throws Exception {
        test1();
        test2();
        test3();
    }

    /**
     * 通过UserDo调用方法
     *
     * @author Anna.
     * @date 2024/4/3 23:54
     */
    public static void test1(){
        UserDo userDo = new UserDo();
        long start = System.currentTimeMillis();
        for (long i = 0; i < 10000000000L; i++) {
            userDo.getName();
        }
        long end = System.currentTimeMillis();
        System.out.printf("通过UserDo调用getName()方法耗时(ms):%s%n", end - start);
    }

    /**
     * 通过反射调用getName方法,不设置跳过安全检查
     *
     * @author Anna.
     * @date 2024/4/3 23:57
     */
    public static void test2() throws Exception {
        // 获取Class
        Class<?> clazz = Class.forName("demo1.UserDo");
        // 初始化对象
        UserDo userDo = (UserDo) clazz.getDeclaredConstructor().newInstance();
        // 获取setName()方法
        Method getNameNameMethod = clazz.getMethod("getName");
        long start = System.currentTimeMillis();
        for (long i = 0; i < 10000000000L; i++) {
            getNameNameMethod.invoke(userDo);
        }
        long end = System.currentTimeMillis();
        System.out.printf("通过反射调用getName方法,不设置跳过安全检查(ms):%s%n", end - start);
    }

    /**
     * 通过反射调用getName方法,设置跳过安全检查
     *
     * @author Anna.
     * @date 2024/4/3 23:57
     */
    public static void test3() throws Exception {
        // 获取Class
        Class<?> clazz = Class.forName("demo1.UserDo");
        // 初始化对象
        UserDo userDo = (UserDo) clazz.getDeclaredConstructor().newInstance();
        // 获取setName()方法
        Method getNameNameMethod = clazz.getMethod("getName");
        // 设置跳过安全检查
        getNameNameMethod.setAccessible(true);
        long start = System.currentTimeMillis();
        for (long i = 0; i < 10000000000L; i++) {
            getNameNameMethod.invoke(userDo);
        }
        long end = System.currentTimeMillis();
        System.out.printf("通过反射调用getName方法,设置跳过安全检查(ms):%s%n", end - start);
    }
}

执行结果:

在这里插入图片描述

结论:
从执行结果可以看出,反射在性能方面是有所损耗的,但是设置setAccessible(true)可以适当提升反射效率。

反射操作泛型

泛型在Java中引入了类型参数的概念,但在运行时,由于类型擦除(Type Erasure),泛型类型信息并不完全保留。

为了通过单设操作这些类型以配合实际开发的需要,Java新增了ParameterizedType,GenericArrayType,TypeVariable,WildcardType几种类型来代表不能为归一到Class类中的类型但是又和原始类型起名的类型。

  • ParameterizedType :表示一种参数化的类型,比如Collection
  • GenericArrayType :表示一种元素类型是参数化类型或者类型变量的数组类型
  • TypeVariable :是各种类型变量的公共接口
  • WildcardType:代表一种通配符类型表达式,比如:?,? extends Number, ?super Integer
package demo4;

import demo1.UserDo;

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

/**
 * 反射操作泛型
 *
 * @author Anna.
 * @date 2024/4/4 0:48
 */
public class ReflexDemo {
    public void test01(Map<String, UserDo> map, List<UserDo> userDoList) {
        System.out.println("调用test01");
    }

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

    public static void main(String[] args) throws Exception {
        // 获取制定法方法参数泛型信息
        Method test01Method = ReflexDemo.class.getMethod("test01", Map.class, List.class);
        // 获取方法的形式参数类型
        Type[] genericParameterTypes = test01Method.getGenericParameterTypes();
        for (Type genericParameter : genericParameterTypes) {
            // 判断形式参数类型是否为参数化的类型
            if (genericParameter instanceof ParameterizedType) {
                // 获取实际的类型参数
                Type[] actualTypeArguments = ((ParameterizedType) genericParameter).getActualTypeArguments();
                for (Type actualTypeArgument : actualTypeArguments) {
                    System.out.printf("参数,泛型类型:%s%n", actualTypeArgument);
                }
            }
        }

        // 获取制定法方法返回值泛型信息
        Method test02Method = ReflexDemo.class.getMethod("test02");
        // 获取方法的形式参数类型
        Type genericReturnType = test02Method.getGenericReturnType();
        // 判断返回值 类型是否为参数化的类型
        if (genericReturnType instanceof ParameterizedType) {
            // 获取实际的类型参数
            Type[] actualTypeArguments = ((ParameterizedType) genericReturnType).getActualTypeArguments();
            for (Type actualTypeArgument : actualTypeArguments) {
                System.out.printf("返回值,泛型类型:%s%n", actualTypeArgument);
            }
        }

    }
}

执行结果

在这里插入图片描述

gitee源码

git clone https://gitee.com/dchh/JavaStudyWorkSpaces.git

  • 27
    点赞
  • 26
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

丨Anna丨

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

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

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

打赏作者

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

抵扣说明:

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

余额充值