java反射

反射

反射的概念

  • Java反射是指在运行时检查或修改类、方法、字段等程序结构的能力。
  • 通过Java反射,可以在运行时获取类的信息、调用方法、访问字段,以及动态创建对象实例,而无需在编译时知道这些信息。
  • 这使得Java程序可以在运行时动态地操作类和对象,从而实现更灵活的编程方式。
  • 当程序在运行时,所有的类、对象、方法等信息都被加载到内存中。通过反射,程序可以在运行过程中访问和修改这些信息。

反射实现的过程

  1. 获取Class对象
    • 在Java中,要使用反射,首先需要获取要操作的类的Class对象。有三种常用的方式可以获取Class对象:
    • 通过对象的getClass()方法: 使用对象的getClass()方法可以获取对应类的Class对象。
    • 通过类名.class: 使用类名.class语法可以获取对应类的Class对象。
    • 通过Class.forName()方法: 使用Class.forName(“类的完整路径”)方法可以根据类的完整路径获取对应类的Class对象。
  2. 获取类的构造函数、方法和字段
    • 一旦有了Class对象,就可以通过反射获取类的构造函数、方法字段等信息:
    • 获取构造函数: 使用getConstructors()方法可以获取类的所有公有构造函数,使用getDeclaredConstructors()方法可以获取类的所有构造函数(包括私有构造函数)。
    • 获取方法: 使用getMethods()方法可以获取类的所有公有方法,使用getDeclaredMethods()方法可以获取类的所有方法(包括私有方法)。
    • 获取字段: 使用getFields()方法可以获取类的所有公有字段,使用getDeclaredFields()方法可以获取类的所有字段(包括私有字段)。
  3. 实例化对象、调用方法和访问字段
    • 通过反射,可以实例化对象、调用方法和访问字段:
    • 实例化对象: 使用Constructor类的newInstance()方法可以实例化对象。
    • 调用方法: 使用Method类的invoke()方法可以调用类的方法。
    • 访问字段: 使用Field类的get()和set()方法可以访问和修改类的字段值。
  4. 动态代理
    • Java反射机制还可以实现动态代理。通过Proxy类和InvocationHandler接口,可以动态地创建代理对象,实现对目标对象的代理操作。
  5. 注意事项
    • 使用反射机制需要注意性能问题,因为反射操作相对于直接调用方法或访问属性来说会更慢一些。
    • 反射机制破坏了封装性,可以访问和修改类的私有方法和字段,因此在使用反射时需要慎重考虑安全性和设计合理的访问控制。
//反射的实例
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Constructor;

public class ReflectionExample {
    private String privateField = "私有字段";
    public String publicField = "公共字段";

    private ReflectionExample() {
        System.out.println("私有构造函数被调用");
    }

    public ReflectionExample(int num) {
        System.out.println("公共构造函数被调用,参数为 " + num);
    }

    private void privateMethod() {
        System.out.println("私有方法被调用");
    }

    public void publicMethod() {
        System.out.println("公共方法被调用");
    }

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

        // 获取私有字段并设置其值
        Field privateField = clazz.getDeclaredField("privateField");
        //覆盖字段、方法和构造函数的默认访问限制,从而访问和修改私有成员。
        privateField.setAccessible(true);
        privateField.set(new ReflectionExample(), "修改后的私有字段值");
        
        // 获取公共字段并读取其值
        Field publicField = clazz.getField("publicField");
        System.out.println("公共字段的值: " + publicField.get(new ReflectionExample()));

        // 获取私有构造函数并实例化对象
        Constructor<?> privateConstructor = clazz.getDeclaredConstructor();
        privateConstructor.setAccessible(true);
        privateConstructor.newInstance();

        // 获取带参数的公共构造函数并实例化对象
        Constructor<?> publicConstructor = clazz.getConstructor(int.class);
        publicConstructor.newInstance(42);

        // 调用私有方法
        Method privateMethod = clazz.getDeclaredMethod("privateMethod");
        privateMethod.setAccessible(true);
        privateMethod.invoke(new ReflectionExample());

        // 调用公共方法
        Method publicMethod = clazz.getMethod("publicMethod");
        publicMethod.invoke(new ReflectionExample());
    }
}

Class类

  • 在Java中,Class 类是一个特殊的类,它代表了一个 Java 类的实例。
  • Class 类的对象包含了关于特定类的结构信息,例如类的字段、方法、构造函数等。
  • 通过 Class 类,可以获取有关类的各种信息,并在运行时动态地操作类。
  • 通过 Class 类,可以实现反射(Reflection)机制,使得在运行时可以动态地获取类的信息、调用方法等

用java类的加载理解Class类

  • 针对编写好的.java源文件进行编译(使用javac.exe),会生成一个或多个.class字节码文件。
  • 接着,我们使用java.exe命令对指定的.class文件进行解释运行。
  • 这个解释运行的过程中,我们需要将.class字节码文件加载到内存中
  • 加载到内存中的.class文件对应的结构即为Class的一个实例
  • 比如:Class myClass = MyClass.class;中的MyClass.class其实是内存里面的一个运行时类
  • 运行时类在内存中会缓存起来,在整个执行期间,只会加载一次

获取类的 Class 对象:

Class<?> myClass = MyClass.class; // 通过类名获取 Class 对象
Class<?> myClass = obj.getClass(); // 通过对象实例获取 Class 对象
Class<?> myClass = Class.forName("com.example.MyClass"); // 通过类的全限定名获取 Class 对象

获取类的信息:

String className = myClass.getName(); // 获取类的名称
Field[] fields = myClass.getDeclaredFields(); // 获取类的字段
Method[] methods = myClass.getDeclaredMethods(); // 获取类的方法

创建类的实例:

MyClass obj = (MyClass) myClass.newInstance(); // 使用无参构造函数创建实例

调用类的方法:

Method method = myClass.getMethod("methodName", parameterTypes);
method.invoke(obj, args);

类的加载过程

  1. 加载(Loading):
    • 加载是指查找并加载类的字节码文件。当程序中使用到某个类时,Java虚拟机会通过类加载器(ClassLoader)来加载这个类。
    • 加载阶段包括以下步骤:
    • 通过类的全限定名(Fully Qualified Name)获取类的二进制字节流。
    • 将字节流转换为方法区中的运行时数据结构。
    • 在内存中生成一个代表这个类的Class对象,作为方法区这个类的各种数据的访问入口。
  2. 链接(Linking):
    • 链接阶段又分为验证(Verification)、准备(Preparation)、解析(Resolution)三个步骤:
    • 验证:确保被加载的类满足Java虚拟机规范,比如格式验证、语义验证、字节码验证等。
    • 准备:为类的静态变量分配内存空间,并设置默认初始值。
    • 解析:将类、接口、字段和方法的符号引用解析为直接引用。
  3. 初始化(Initialization):
    • 在初始化阶段,Java虚拟机会对类进行初始化,包括执行类构造器<clinit>()方法。
    • 类的初始化是按需进行的,即在首次主动使用类时才会进行初始化,主动使用包括创建类的实例、访问类的静态变量、调用类的静态方法等。

类加载器

类加载器的工作流程

  • 加载(Loading):查找并加载类的二进制数据。
  • 链接(Linking):
    • 验证(Verification):确保加载的类符合Java虚拟机规范。
    • 准备(Preparation):为类的静态变量分配内存并设置默认初始值。
    • 解析(Resolution):将符号引用解析为直接引用。
  • 初始化(Initialization):执行类构造器方法 (),也就是静态代码块。

双亲委派模型(Parent Delegation Model)

  • 双亲委派模型是类加载器的一种组织结构,其核心思想是:当一个类加载器收到加载类的请求时,它会先委托给父类加载器去加载,只有在父类加载器无法加载时,才会自己尝试加载。这种模型的优点在于保证Java核心类库不会被篡改,同时避免类的重复加载。

类加载器的分类

  • 引导类加载器(Bootstrap Class Loader):负责加载核心Java类,由C++实现,无法直接获取。
  • 扩展类加载器(Extension Class Loader):负责加载Java的扩展类,通常加载$JAVA_HOME/jre/lib/ext目录下的类。
  • 应用程序类加载器(Application Class Loader):负责加载应用程序中的类,是Java应用程序默认的类加载器。
  • 自定义类加载器(Custom Class Loader):开发者可以根据需要编写自定义的类加载器,实现特定的加载行为。

类加载器的委托机制

  1. 当一个类加载器收到加载类的请求时,它会先询问父类加载器是否能够加载该类。
  2. 如果父类加载器无法加载,则该类加载器尝试加载类。
  3. 这个过程一直持续,直到达到顶层的引导类加载器。

类加载器的破坏

  • 破坏双亲委派模型:破坏双亲委派模型可能导致类的重复加载和安全性问题。
  • 破坏类加载器的唯一性:如果两个类加载器加载同一个类,会导致类的类型不一致,可能引发类型转换异常等问题。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值