【Java基础系列】- 反射机制

【Java基础系列】- 反射机制


一、程序运行过程

ClassLoader找到主类将其Load到内存中的CodeSegment,运行环境找到main方法并开始执行,运行过程中会有更多class被load到内存

  • ClassLoader:负责将Java字节码(.class文件)加载到JVM中,具有双亲委派机制

  • Class:表示Java中的类和接口,通过反射机制可以动态地获取类的信息和操作类的成员

二、ClassLoader

站在classloader角度来看,CodeSegment中的每个.class就是一个class对象
在这里插入图片描述

ClassLoader的类加载机制
  • 并非一次性加载到内存,需要时加载(运行期间动态加载)
  • static语句块在加载后无论new几次只执行一次
  • dynamic语句块每次new新对象都会执行,等同于构造方法中语句
JDK ClassLoader的层次关系

类加载器是以双亲委派模型工作的,确保了类加载的安全性和稳定性:

  1. 当一个类加载器需要加载一个类时,它首先将这个请求委托给它的父类加载器
  2. 如果父类加载器找不到这个类,它再尝试自己加载

!!不是继承,继承指的是类之间的关系,在这说的是对象之间的关系

  • application class loader内有一个引用parent指向extesion class loaderextesion class loader内有一个引用指向bootstrap class loader,即指向其parent对象,通过getParent()方法获取

  • application class loader加载用户自定义类时会先找到其parent的class loader询问其该类有没有被加载,如果已经有同名的被加载了,则该自定义类就不会被加载(双亲委派机制)

在这里插入图片描述

ClassLoader分类

ClassLoader是一个抽象类,它的主要作用是将Java字节码加载到Java虚拟机(JVM)中。Java中有三种主要的类加载器:

  1. 启动类加载器(Bootstrap ClassLoader)

    • 加载JDK的核心类,把其他classloader加载进来

    • 负责加载核心Java类库,如java.langjava.util等。它是用本地代码实现的,是虚拟机的一部分

  2. 扩展类加载器(Extension ClassLoader)

    • 负责加载扩展类库,默认加载JAVA_HOME/lib/ext目录下的所有类库
  3. 应用程序类加载器(Application ClassLoader)

    • 负责加载应用程序类路径(classpath)上的类,用户自定义类,它是最常用的类加载器
常用方法

ClassLoader类提供了一些常用的方法:

  • loadClass(String name): 加载一个类
  • getParent(): 获取父类加载器
  • getResource(String name): 查找资源文件
  • getResources(String name): 查找所有匹配的资源文件

自定义类加载器:

有时需要自定义类加载器来加载某些特殊来源的类。如,从网络或加密的JAR文件中加载类:

public class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] bytes = loadClassData(name); // 自定义加载类数据的逻辑
        return defineClass(name, bytes, 0, bytes.length);
    }

    private byte[] loadClassData(String name) {
        // 加载类数据的逻辑
        return new byte[0]; // 示例代码
    }
}

三、Class

Class类是一个非常重要的类,它表示正在运行的Java应用程序中的类和接口

每个类或接口在JVM中都有一个Class对象与之对应,这个Class对象在类加载时由JVM自动创建

获取Class对象的三种方式
  1. 通过类名的.class属性

    Class<?> clazz = String.class;
    
  2. 通过对象的getClass()方法

    String str = "Hello";
    Class<?> clazz = str.getClass();
    
  3. 通过Class.forName()方法

    Class<?> clazz = Class.forName("java.lang.String");
    
常用方法

Class类提供了许多方法来反射地获取类的信息:

  • getName(): 获取类的全限定名。
  • getSuperclass(): 获取父类的Class对象。
  • getInterfaces(): 获取实现的接口。
  • getDeclaredFields(): 获取所有声明的字段。
  • getDeclaredMethods(): 获取所有声明的方法。
  • getDeclaredConstructors(): 获取所有声明的构造方法。
  • newInstance(): 创建类的新实例(已过时,推荐使用Constructor.newInstance())。

以下是一个使用Class类和反射获取类信息的示例:

public class ReflectionExample {
    public static void main(String[] args) {
        try {
            Class<?> clazz = Class.forName("java.util.ArrayList");

            System.out.println("Class Name: " + clazz.getName());
            System.out.println("Superclass: " + clazz.getSuperclass().getName());

            System.out.println("Interfaces: ");
            for (Class<?> iface : clazz.getInterfaces()) {
                System.out.println(iface.getName());
            }

            System.out.println("Methods: ");
            for (Method method : clazz.getDeclaredMethods()) {
                System.out.println(method.getName());
            }

            System.out.println("Fields: ");
            for (Field field : clazz.getDeclaredFields()) {
                System.out.println(field.getName());
            }
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}

四、反射

  • 反射机制使java变成动态语言,允许程序在执行过程中借助Reflection API取得任何类的内部信息,

  • 允许程序在运行时检查和修改自身的结构和行为

  • 通过反射,可以在运行时动态地获取类的信息、创建对象、调用方法和访问字段(内部属性)

  • 反射主要用于框架、库和工具开发,但也可以用于调试和测试,使得程序在运行时能够动态地操作类和对象

  • 它也有其局限性和开销

加载完类之后,堆内存中就产生了一个Class类对象,一个类只有一个Class对象,通过该对象可以看到类的结构:

  • 正常方式:引入需要的包类名称->通过new实例化->取得实例化对象
  • 反射方式:实例化对象->getClass()方法->得到完整的包类名称
主要类和方法

反射主要依赖于以下几个类和接口:

  1. Class类
    • 获取类的字节码信息
    • forName(String className): 获取指定类名的Class对象
    • newInstance(): 创建Class对象所表示的类的一个新实例
  2. Constructor类
    • 表示类的构造方法
    • newInstance(Object... initargs): 创建新实例
  3. Method类
    • 表示类的方法
    • invoke(Object obj, Object... args): 调用该方法
  4. Field类
    • 表示类的字段,静态常量
    • get(Object obj): 获取字段的值
    • set(Object obj, Object value): 设置字段的值

以下是一些基本的反射操作示例:

获取类的Class对象

Class<?> clazz = Class.forName("com.example.MyClass");

创建类的实例

Object obj = clazz.newInstance();

获取并调用方法

Method method = clazz.getMethod("myMethod", String.class);
method.invoke(obj, "Hello, World!");

获取并设置字段的值

Field field = clazz.getDeclaredField("myField");
field.setAccessible(true); // 如果字段是私有的,需要设置可访问性
field.set(obj, "New Value");

获取构造方法并创建实例

Constructor<?> constructor = clazz.getConstructor(String.class);
Object obj = constructor.newInstance("Constructor Argument");
注意事项
  1. 性能:反射操作通常比直接调用慢,因为它涉及动态类型检查。
  2. 安全性:反射可以绕过正常的访问控制(例如访问私有字段和方法),这可能会导致安全问题。
  3. 代码可维护性:过度使用反射会使代码难以理解和维护。
使用场景
  1. 框架开发:例如Spring和Hibernate使用反射来动态管理和注入依赖。
  2. 序列化和反序列化:例如Gson和Jackson通过反射将对象转换为JSON格式,反之亦然。
  3. 单元测试:例如JUnit可以通过反射调用私有方法进行测试。

五、反射带来的动态性

反射使Java具备了动态语言的一些特性。虽然Java是一种静态类型语言(即类型检查发生在编译时),但反射提供了在运行时动态操作类和对象的能力。这使得Java可以在某些方面表现得像动态语言。以下是反射如何使Java具备动态语言特性的几个方面:

  1. 动态类加载

通过反射,Java可以在运行时加载和使用未知的类。这使得程序可以根据运行时条件加载不同的类,而不是在编译时确定所有依赖关系。

Class<?> clazz = Class.forName("com.example.SomeClass");
Object obj = clazz.newInstance();
  1. 动态方法调用

反射允许在运行时调用方法,而不需要在编译时知道方法的确切签名。这样可以实现更加灵活的代码调用。

Method method = clazz.getMethod("someMethod", String.class);
method.invoke(obj, "Hello");
  1. 动态字段访问

反射使得可以在运行时访问和修改对象的字段,即使这些字段在编译时未知。

Field field = clazz.getDeclaredField("someField");
field.setAccessible(true);
field.set(obj, "New Value");
  1. 动态构造函数调用

可以在运行时选择并调用构造函数,创建新的对象实例。

Constructor<?> constructor = clazz.getConstructor(String.class);
Object newInstance = constructor.newInstance("Constructor Argument");
  1. 动态代理

Java反射还支持动态代理(Dynamic Proxy),允许在运行时创建实现一个或多个接口的对象。这对于AOP(面向方面编程)和拦截器模式非常有用。

InvocationHandler handler = new MyInvocationHandler();
MyInterface proxy = (MyInterface) Proxy.newProxyInstance(
        MyInterface.class.getClassLoader(),
        new Class<?>[]{MyInterface.class},
        handler);
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值