Java核心(四)反射

这篇内容叫反射也不够准确,其实它更像是java类加载的一个延申。

Java类加载过程

之前解释过一个Java的类的加载过程,现在回顾一下类的加载:

类的加载指的是将类的字节码文件(.class文件)中数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类的加载的最终产品是位于堆区中的Class对象。下面来看一下这个Class对象

Class对象

在java世界里,一切皆对象。从某种意义上来说,java有两种对象:实例对象和Class对象。对于每个类而言,JRE 都为其保留一个不变的 Class 类型的对象。

所谓Class对象可以理解为一个Java类在JVM层面上的存在形态,它以虚拟机能理解的形式描述着一个类。运行时产生的每个实例都会通过类元数据中的对象头来记录自己所属的Class对象,我们编写的Java类、这个类的Class对象、这个类在运行时所诞生的具体对象,这三者之间的关系:

每一个类都有一个Class对象,每当编译一个新类就产生一个Class对象,基本类型 (boolean, byte, char, short, int, long, float, and double)有Class对象,数组有Class对象,就连关键字void也有Class对象(void.class)。Class对象对应着java.lang.Class类,如果说类是对象抽象和集合的话,那么Class类就是对类的抽象和集合。

Class类没有公共的构造方法,Class对象是在类加载的时候由Java虚拟机以及通过调用类加载器中的 defineClass 方法自动构造的,因此不能显式地声明一个Class对象。

在类加载阶段,类加载器首先检查这个类的Class对象是否已经被加载。如果尚未加载,默认的类加载器就会根据类的全限定名查找.class文件。在这个类的字节码被加载时,它们会接受验证,以确保其没有被破坏,并且不包含不良java代码。

Class对象有以下特点:

  1. Class 对象只能由系统建立对象
  2. 一个类在 JVM 中只会有一个Class实例

对象的形成过程

这里要声明一点,自然运行产生的每个实例并不是由Class对象创造,而是JVM通过解析语句,发现'new'操作时,进行创建,创建过程包括:

  • 分配内存:JVM为新对象在堆内存中分配足够的空间。
  • 初始化对象头:设置对象的元数据信息,如对象的类型信息等。
  • 执行构造器<init>():调用对象的构造器方法,进行对象的初始化。

对象头包含了对象的元数据信息,其中就包括指向该对象Class对象的引用,这个引用允许JVM和Java程序识别对象的类型。

反射的本质

在前面说 Java类加载的时候提过,说过类的加载指的是将类的字节码文件(.class文件)中数据读入到内存中,将其放在运行时数据区的方法区内,然后在堆区创建一个java.lang.Class对象,用来封装类在方法区内的数据结构。类加载的最终结果就是class对象,而关于Class对象的使用就涉及反射的内容了。

正射

理解反射,首先得知道它的对立面,“正射”,前面了解过了,系统会为每个java类自动创建唯一一个Class对象,它包含了与类有关的信息。此时的java类处于一个中间状态,并不是我们使用的对象,只有当我们使用  “ new  Object()”时,才会在JVM堆中根据这个Class对象来产生真正供我们使用的实例对象。其实也就是上面部分的对象的形成过程。

正射的使用含义是,我事先定义了一个对象的某些东西,然后当我需要的时候,我会通知内存去创建这个对象,然后我事先知道这个对象有什么,所以我会精准的调用它的某个方法,某个成员变量。看一个例子:

class Human {
    String name;
    int age;
    String nation;
    Human(String name,int age,String nation) {
        this.name=name;
        this.age=age;
        this.nation=nation;
    }
    void changeName(String name){
        this.name=name;
    }
}

public class Main {
    public static void main(String[] args){
        Human human=new Human("张三",22,"中国");
        human.changeName("李四");
    }
}

在上面Main类的main方法中,之所以可以直接写human.changeName(“张三”) 是因为Human类是我设计编写的,作为编写者清楚的知道human作为Human的实例对象,可以调用changeName方法,如果我手抖写错了changeName的方法名,我也会立即改回来,因为我知道Human里没有这个方法。假如我不知道Human里有没有一个改名字的方法,即Human类对我来说是不透明的第三方类,我尝试性的在程序中调用了一个newName方法,保存、编译。这时候编译器会通知我,这样写程序是不对的,Human里没有一个叫newName的方法,编译失败。 

反射

所谓反射,官方的定义是: 指计算机程序在运行时(runtime) 可以访问、检测和修改它本身状态或行为的一种能力。通俗说,反射就是程序在运行的时候能够“观察”并且修改自己的行为,是程序对自身的反思、自检、修改。

反射则与正射相反,是一个类于我而言是透明的,我全然不知道它有什么,反射就像一个镜面一样,告诉我它有什么,像官方解释的更准确地理解,就是当程序运行之后,通过“镜面”,仍旧可以在运行时,插足JVM内部的实例对象相关信息。

反射的应用

像上文中提到的,Java的反射机制十分强大,允许程序在运行时动态地访问和操作类、接口、方法、构造函数和字段。使用反射的切入点就是上文中提到的Class对象,利用JVM中的Class对象,可以在运行时,具备动态的处理实例的能力。

反射一般应用在:

  • 框架开发:例如Spring中核心的依赖注入、Mybaits中的ORM映射等
  • 动态代理: Java中的AOP实现

  • 配置文件与代码的映射:将配置文件中的信息映射到Java类中。例如,XML或JSON配置文件中的属性可以动态地设置到Java对象的字段中。

  • 动态类加载:一些组件、框架的动态加载,可能需要在运行时从网络或其他源动态加载类。反射可以用来加载这些类并创建它们的实例。

  • 调用私有内容:在做一些特殊功能或工具时,不可避免的要违背Java的封装特性,就可以使用反射进行实现

针对Class对象反射常用的一些方法:

类/接口名方法名入参类型返回类型描述
ClassforName(String className)StringClass<?>通过类名获取Class对象,可能抛出ClassNotFoundException
ClassgetDeclaredClasses()Class<?>[]获取类中声明的所有内部类和接口的Class对象数组。
ClassgetDeclaredField(String name)StringField获取类中声明的指定公共字段。
ClassgetDeclaredMethod(String name, Class<?>... parameterTypes)String, Class<?>...Method获取类中声明的指定公共方法。
ClassgetDeclaredConstructor(Class<?>... parameterTypes)Class<?>...Constructor<?>获取类中声明的指定公共构造函数。
Fieldget(Object obj)ObjectObject获取对象的字段值,可能抛出IllegalAccessException
Fieldset(Object obj, Object value)Object, Object设置对象的字段值,可能抛出IllegalAccessException
FieldgetName()String获取字段的名字。
FieldgetModifiers()int获取字段的访问修饰符。
Methodinvoke(Object obj, Object... args)Object, Object...Object调用方法,可能抛出IllegalAccessExceptionInvocationTargetException
MethodgetName()String获取方法的名字。
MethodgetModifiers()int获取方法的访问修饰符。
ConstructornewInstance(Object... initargs)Object...T创建类的实例,可能抛出InstantiationExceptionIllegalAccessException
ConstructorgetParameterTypes()Class<?>[]获取构造函数的参数类型。
ArraygetLength(Object array)Objectint返回数组的长度。
Arrayget(Object array, int index)Object, intObject获取数组元素。
Arrayset(Object array, int index, Object value)Object, int, Object设置数组元素。
ModifierisPublic(int mod)intboolean测试是否设置为public。
ModifierisPrivate(int mod)intboolean测试是否设置为private。
ModifierisProtected(int mod)intboolean测试是否设置为protected。
ModifierisStatic(int mod)intboolean测试是否设置为static。
AccessibleObjectisAccessible()boolean检查对象是否可访问。
AccessibleObjectsetAccessible(boolean flag)boolean设置对象是否可访问,如果设置为true,可以绕过Java语言访问检查。
AnnotationgetDeclaredAnnotations()Annotation[]获取类声明的所有注解。
AnnotationgetAnnotation(Class<T> annotationClass)ClassT获取类上指定类型的注解,如果不存在则返回null。

 

关于反射API配套的

下面来看使用反射越级访问私有变量的例子:

定义一个普通的Service类:

public class DemoService{
    private void secretMethod() {
        System.out.println("I'm a secret method!");
    }
}

我们想要在另一个类中调用demoService的secretMethod方法。由于这个方法是私有的,我们无法直接调用它,但可以使用反射来实现:

public class ReflectionDemo {
    public static void main(String[] args) {
        try {
            // 获取SecretKeeper类的Class对象
            Class<?> secretKeeperClass = Class.forName("com.dome.DemoService");

            // 获取SecretKeeper类中名为secretMethod的私有方法的Method对象
            Method secretMethod = secretKeeperClass.getDeclaredMethod("secretMethod");

            // 改变访问权限,以便可以访问私有方法
            secretMethod.setAccessible(true);

            // 创建SecretKeeper类的实例
            Object secretKeeperInstance = secretKeeperClass.newInstance();

            // 调用secretMethod方法
            secretMethod.invoke(secretKeeperInstance);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

另一个更常见的例子,如果阅读过Spring早期版本源码的同学应该见过,Spring最核心的Bean管理,实现原理就是,根据外部的xml配置文件,将类进行指定,然后通过反射来完成类的实例化以及依赖的处理。这里简化实现一下:

首先定义一个普通的Java类:


public class MyObject {
    public void doSomething() {
        System.out.println("MyObject is doing something.");
    }
}

对应一个用于控制的配置文件:

objectClass=com.example.MyObject

通过反射,动态生产对象实例:


public class ObjectFactory {
    public static Object createObject(String configFilePath) throws Exception {
        // 加载配置文件
        Properties props = new Properties();
        try (FileInputStream fis = new FileInputStream(configFilePath)) {
            props.load(fis);
        }

        // 获取类名
        String className = props.getProperty("objectClass");

        // 使用反射加载类
        Class<?> clazz = Class.forName(className);

        // 创建实例
        Object obj = clazz.getDeclaredConstructor().newInstance();

        return obj;
    }
}

public class Main {
    public static void main(String[] args) {
        try {
            // 假设配置文件位于当前目录
            Object myObject = ObjectFactory.createObject("config.properties");
            
            // 假设创建的对象有doSomething方法
            ((MyObject) myObject).doSomething();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值