java基础之 反射

本文详细介绍了Java反射机制,包括Class类的作用、获取Class对象的方式、类加载过程、类加载器及双亲委派模型,还提供了如何通过反射访问和操作类的私有成员以及公共方法的示例,同时讨论了反射的性能开销和潜在的安全隐患。
摘要由CSDN通过智能技术生成

反射机制

Java反射机制是Java语言的一个特性,它允许程序在运行时对任意类的内部细节进行访问和操作。这意味着你可以在运行时查看、修改和调用对象的属性和方法,而无需在编译时知道这些信息。

反射基础

Class类

Class类在Java反射机制中扮演着核心的角色。它是Java所有类型的元信息类,也就是说,每个类都有一个与之对应的Class对象。这个Class对象包含了关于类的所有信息,如类名、父类、实现的接口、类中的字段、方法、构造器等。

public final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    private static final int ANNOTATION= 0x00002000;
    private static final int ENUM      = 0x00004000;
    private static final int SYNTHETIC = 0x00001000;

    private static native void registerNatives();
    static {
        registerNatives();
    }

    /*
     * Private constructor. Only the Java Virtual Machine creates Class objects.   //私有构造器,只有JVM才能调用创建Class对象
     * This constructor is not used and prevents the default constructor being
     * generated.
     */
    private Class(ClassLoader loader) {
        // Initialize final field for classLoader.  The initialization value of non-null
        // prevents future JIT optimizations from assuming this final field is null.
        classLoader = loader;
    }

Class类对象的获取

获取class对象的三种方式

  • 根据类名:类名.class
  • 根据对象:对象.getClass()
  • 根据全限定类名:Class.forName(全限定类名)
    @Test
    public void classTest() throws Exception {
        // 获取Class对象的三种方式
        logger.info("根据类名:  \t" + User.class);
        logger.info("根据对象:  \t" + new User().getClass());
        logger.info("根据全限定类名:\t" + Class.forName("com.test.User"));
        // 常用的方法
        logger.info("获取全限定类名:\t" + userClass.getName());
        logger.info("获取类名:\t" + userClass.getSimpleName());
        logger.info("实例化:\t" + userClass.newInstance());
    }

输出结果

根据类名:  	class com.test.User
根据对象:  	class com.test.User
根据全限定类名:	class com.test.User
获取全限定类名:	com.test.User
获取类名:	User
实例化:	User [name=init, age=0]

类加载

1. 加载(Load)

通过类的全限定名来获取二进制字节流(.class文件),在java对中生成代表这个类的java.lang.Class对象。

2. 链接(Link)

验证(verify)

确保被加载的类文件满足JVM规范(文件格式验证、元数据验证、字节码验证、符号引用验证),没有安全方面的问题

准备(prepare)

准备阶段是为类的静态变量分配内存,并将其初始化为默认值(如数值类型变量初始化为0,应用类型变量初始化为null)。

解析(resolve)

解析是将符号引用转换为直接引用的过程。符号引用是类文件中的常量池中的一项,它以一组字符串的形式表示所引用的目标。直接引用是指向目标的指针、相对偏移量或一个间接定位到目标的句柄。

3. 初始化(Initialize)

初始化阶段是执行类构造器方法(<clinit>()方法)的过程。该方法由编译器自动收集类中的所有类变量的赋值动作和静态代码块集合而成。初始化阶段是类加载过程的最后一步,执行完毕后,一个类就可以被JVM使用了。

类加载器(ClassLoader)

类加载器是负责加载类文件的机制。JVM中主要有三种类加载器:

  • 引导类加载器(Bootstrap ClassLoader):最顶层的加载类,主要加载核心类库。
  • 扩展类加载器(Extension ClassLoader):从java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的lib/ext子目录下加载类库。
  • 系统类加载器(System ClassLoader):也叫应用类加载器(Application ClassLoader),一般来说,Java应用的类都是由它来完成加载的。

此外,用户还可以自定义类加载器来加载特定的类。自定义类加载器通常需要继承java.lang.ClassLoader类,并重写其findClass()方法。

双亲委派模型(Parent Delegation Model)

双亲委派模型是Java类加载器的一种实现方式。当一个类加载器收到了类加载请求时,它不会自己先去加载这个类,而是把这个请求委派给父类加载器去完成,每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的启动类加载器中。只有当父类加载器无法完成这个加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会尝试自己去加载。这种模型确保了Java核心API的稳定性和安全性,因为核心API的类总是由启动类加载器加载的,而启动类加载器是由JVM实现并内置的,其安全性非常高。

反射使用

简单示例

定义一个Java类

public class ExampleClass {
    private String privateField = "Private Field";
    public String publicField = "Public Field";

    private void privateMethod() {
        System.out.println("Private Method");
    }

    public void publicMethod() {
        System.out.println("Public Method");
    }

    public static void staticMethod() {
        System.out.println("Static Method");
    }
}

通过反射来访问这个类的私有字段、私有方法,以及调用公有方法和静态方法

import java.lang.reflect.Method;

public class ReflectionExample {
    public static void main(String[] args) {
        try {
            // 获取ExampleClass类的Class对象
            Class<?> exampleClass = ExampleClass.class;

            // 创建ExampleClass的实例
            Object instance = exampleClass.getDeclaredConstructor().newInstance();

            // 访问私有字段
            java.lang.reflect.Field privateField = exampleClass.getDeclaredField("privateField");
            privateField.setAccessible(true); // 设置为可访问(包括私有字段)
            String privateFieldValue = (String) privateField.get(instance);
            System.out.println("Private Field Value: " + privateFieldValue);

            // 访问并调用私有方法
            java.lang.reflect.Method privateMethod = exampleClass.getDeclaredMethod("privateMethod");
            privateMethod.setAccessible(true); // 设置为可访问(包括私有方法)
            privateMethod.invoke(instance);

            // 访问并调用公有方法
            java.lang.reflect.Method publicMethod = exampleClass.getMethod("publicMethod");
            publicMethod.invoke(instance);

            // 直接调用静态方法
            Method staticMethod = exampleClass.getMethod("staticMethod");
            staticMethod.invoke(null); // 静态方法不需要实例,传递null

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

解释

  • 通过.class语法获取ExampleClass类的Class对象。
  • 使用getDeclaredConstructor()获取默认构造方法,并通过newInstance()创建ExampleClass的实例。
  • 使用getDeclaredField()获取私有字段,并通过setAccessible(true)使其可访问,然后获取字段值。
  • 使用getDeclaredMethod()获取私有方法,并通过setAccessible(true)使其可访问,然后调用该方法。
  • 使用getMethod()获取公有方法,并直接调用该方法。
  • 对于静态方法,使用getMethod()获取方法,并传递null作为调用方法的对象,因为静态方法不需要实例即可调用。

注意:反射操作可能会抛出各种异常,如NoSuchMethodExceptionIllegalAccessExceptionInstantiationExceptionInvocationTargetException等,因此在实际使用时需要妥善处理这些异常。

不足

  • 性能开销:反射操作通常比直接操作要慢,因为需要进行更多的运行时检查和解析。
  • 破坏封装性:通过反射可以访问和修改私有字段和方法,这可能会破坏类的封装性。
  • 安全隐患:反射可以被用于执行一些危险的操作,如调用私有方法、修改静态字段等,因此需要谨慎使用。

  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值