Java类加载机制和反射机制

1 类加载

在使用反射的时候,或许我们经常会遇到类初始化类加载这些字眼,如果没搞明白它们是什么,我们是不能很好的在恰当的时机使用反射的。所以我们要先搞懂什么是类、类加载、类初始化。具体可以查看另一篇文章 JVM 字节码文件与类加载

2 反射机制

了解了类加载的机制,我们就可以开始了解反射。

2.1 反射机制的定义

java反射是java被视为动态(或准动态)语言的一个关键性质。反射机制允许程序在运行时(不是编译时)透过 Reflection APIs 取得任何一个已知名称的class内部信息,包括 modifiers(权限修饰符如 publicstatic 等)、superclass(例如 Object)、实现的 interfaces(如 Cloneable)、fields(属性)、methods(方法),并可以在运行时改变 field 和使用 methods

根据反射的定义,反射最主要的关键在于两点:动态运行时,前提是获取已知名称的class

2.2 为什么要反射?

为什么要使用反射?因为我们需要使用的类没有被加载过,或者不能访问,而我们需要访问到类的信息。

结合上面的类加载机制和反射的定义,具体说明是:

我们需要访问的类(比如 Test)还没有被执行到,为了能够访问到它的信息,所以通过反射的方式将 Test 类的 Test.class 字节码文件提前的在类加载器加载到JVM中,生成 Test 类对应的Class对象,通过访问这个Class对象我就能访问到 Test 的具体类信息了。

3 使用反射

在实际开发中,使用反射这项技术无非就是访问类的方法、成员等,在使用操作步骤上大概可以总结为以下几点:

  • 获取Class

  • 通过Class获取obj

  • 通过obj反射获取对象的成员、方法等

3.1 获取Class

以下是三种获取Class的方式:

// ClassLoader将类装载入内存,但不进行类的初始化
// 即类完成了加载阶段被加载进JVM内存创建了Class
Class clazz = Test.class;

// ClassLoader将类装载入内存,并进行类的初始化
// 即类完成了加载阶段并加载进JVM内存创建了Class,并且完成了类初始化阶段
Class clazz = Class.forName("com.example.Test");

// 返回类对象运行时真正所指的对象、所属类型的Class对象
Class clazz = new Test().getClass();

这三种加载方式有什么区别?在说明之前,提供一个测试用例用来验证三种方式:

public class Test {

    static {
        System.out.println("static block initialized....");
    }

    {
        System.out.println("non static block initialized...");
    }

    public Test() {
        System.out.println("constructor initialized...");
    }
}

3.1.1 xxx.class

Class<Test> clazz = Test.class;
System.out.println(clazz);

运行结果:
class com.example.Test

可以发现,使用字面量 Test.class 可以获取到Class对象的类型信息,但没有执行静态代码块相关的代码。

对应到类的生命周期,说明它已经完成了类加载阶段创建了Class对象,所以才能访问到具体类型。而没有执行静态代码块相关的代码,说明类没有完成初始化阶段。

使用这种方式获取Class有它的局限性:

必须要能访问到具体类型的Class调用 xxx.class,如果想要反射一些第三方库或源码内部的类是做不到的。

3.1.2 Class.forName()

我们看下 Class.forName() 的源码:

@CallerSensitive
public static Class<?> forName(String className)
            throws ClassNotFoundException {
    return forName(className, true, VMStack.getCallingClassLoader());
}

/**
 * @Param initialize if {@code true} the class will be initialized.
 */
@CallerSensitive
public static Class<?> forName(String name, boolean initialize,
                               ClassLoader loader)
    throws ClassNotFoundException
{
    if (loader == null) {
        loader = BootClassLoader.getInstance();
    }
    Class<?> result;
    try {
    	// native获取Class
        result = classForName(name, initialize, loader);
    } catch (ClassNotFoundException e) {
        Throwable cause = e.getCause();
        if (cause instanceof LinkageError) {
            throw (LinkageError) cause;
        }
        throw e;
    }
    return result;
}

@FastNative
static native Class<?> classForName(String className, boolean shouldInitialize,
        ClassLoader classLoader) throws ClassNotFoundException;

我们调用 Class.forName() 是一个重载方法,其中有一个参数 initialized,这个参数代表是否对加载的类进行初始化,设置为true时会类进行初始化,代表会执行类中的静态代码块以及对静态变量的赋值等操作。我们验证一下。

try {
    Class<?> clazz = Class.forName("com.example.Test");
    System.out.println(clazz);
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}

运行结果:
static block initialized....
class com.example.Test

可以发现,类的静态代码块被执行了,但是构造函数没有被调用,但也说明完成了类加载和初始化阶段。

3.1.3 obj.getClass()

Test test = new Test();
System.out.println(test.getClass());

运行结果:
static block initialized....
non static block initialized...
constructor initialized...
class com.example.Test

可以发现,类的静态代码块、非静态代码块、构造函数都已经被调用,类已经完成加载阶段、初始化阶段,可以直接使用。

3.2 通过Class获取obj

同样的还是分成两种方式获取。

3.2.1 xxx.class获取obj

Class<Test> clazz = Test.class;

Object obj = clazz.newInstance();Constructor<?> constructor = clazz.getDeclaredConstructor(); // 可传入构造参数类型
Object obj = constructor.newInstance(); // 可传入构造函数参数

使用 clazz.newInstance() 有局限性:

需要提供无参构造方法并且是公开可访问的。

3.2.2 Class.forName()获取obj

Class<?> clazz = Class.forName("com.example.Test");
Constructor<?> constructor = clazz.getDeclaredConstructor(); // 可传入构造参数类型
return constructor.newInstance(); // 可传入构造函数参数

3.3 成员、方法、构造访问方式和限制

3.3.1 Field成员访问方式和限制

方法本classsuper class
getFieldpublicpublic
getDeclaredFieldpublic protected privateno
getFieldspublicpublic
getDeclaredFieldspublic protected privateno

getField 只能获取对象中public的修饰符的属性,并且能获取父类Class的public属性;getDeclaredField 能获取对象中各种修饰符的属性,但无法获取父类的任何属性。

获取父类属性,可以通过class对象提供的 getSuperClass 方法获取到父类对象,然后再通过 getDeclaredField 获取属性:

Class clazz = Class.forName("com.example.Reflect");
Class superClazz = clazz.getSuperClass();
Field field = sueprclazz.getDeclaredField("field");

3.3.2 Method方法访问和限制

方法本classsuper class
getMethodpublicpublic
getDeclaredMethodpublic protected privateno
getMethodspublicpublic
getDeclaredMethodspublic protected privateno

3.3.3 构造方法访问和限制

方法本classsuper class
getConstructorpublicno
getDeclaredConstructorpublic protected privateno
getConstructorspublicno
getDeclaredConstructorspublic protected privateno

需要注意的是,构造方法无法调用父类的任何构造方法。

3.4 通过obj反射获取对象的成员、方法等

下面的代码中会出现 setAccessible(true),在安全管理中会使用checkPermission方法来检查权限,而 setAccessible(true) 并不是将方法的权限改为public,而是取消java的权限控制检查。

3.4.1 xxx.class获取对象的成员、方法等

Class<Test> clazz = Test.class;
Object obj = clazz.newInstance();

// 访问非静态成员变量
Field instanceField = clazz.getDeclaredField("instanceField");
// instanceField.setAccessible(true); // 如果成员是私有的,在访问前修改为可访问
instanceField.get(obj);

// 访问静态成员变量
Field staticField = clazz.getDeclaredField("staticField");
// staticField.setAccessible(true); // 如果成员是私有的,在访问前修改为可访问
staticField.get(obj);

// 访问非静态方法
Method invokeMethod = clazz.getDeclaredMethod("invokeMethod");
// invokeMethod.setAccessible(true); // 如果方法是私有的,在访问前修改为可访问
invokeMethod.invoke(obj);

// 访问有参数的非静态成员方法
Method invokeMethodWithParam = clazz.getDeclaredMethod("invokeMethodWithParam", String.class);
// invokeMethodWithParam.setAccessible(true); // 如果方法是私有的,在访问前修改为可访问
invokeMethodWithParam.invoke(obj, "my param");

// 访问静态方法
Method invokeStaticMethod = clazz.getDeclaredMethod("invokeStaticMethod");
// invokeStaticMethod.setAccessible(true); // 如果方法是私有的,在访问前修改为可访问
invokeStaticMethod.invoke(obj);

// 访问属性中的修饰符
// getModifiers()返回的是一个int类型的返回值,代表类、成员变量、方法的修饰符
String fieldModifier = Modifier.toString(instanceField.getModifiers()); 

3.4.2 Class.forName()获取对象的成员、方法等

Class<?> clazz = Class.forName("com.example.Test");
Constructor<?> constructor = clazz.getDeclaredConstructor();
Object obj = constructor.newInstance();

// 访问非静态成员变量
Field instanceField = clazz.getDeclaredField("instanceField");
// instanceField.setAccessible(true); // 如果成员是私有的,在访问前修改为可访问
instanceField.get(obj);

// 访问静态成员变量
Field staticField = clazz.getDeclaredField("staticField");
// staticField.setAccessible(true); // 如果成员是私有的,在访问前修改为可访问
staticField.get(obj);

// 访问非静态方法
Method invokeMethod = clazz.getDeclaredMethod("invokeMethod");
// invokeMethod.setAccessible(true); // 如果方法是私有的,在访问前修改为可访问
invokeMethod.invoke(obj);

// 访问有参数的非静态成员方法
Method invokeMethodWithParam = clazz.getDeclaredMethod("invokeMethodWithParam", String.class);
// invokeMethodWithParam.setAccessible(true); // 如果方法是私有的,在访问前修改为可访问
invokeMethodWithParam.invoke(obj, "my param");

// 访问静态方法
Method invokeStaticMethod = clazz.getDeclaredMethod("invokeStaticMethod");
// invokeStaticMethod.setAccessible(true); // 如果方法是私有的,在访问前修改为可访问
invokeStaticMethod.invoke(obj);

// 访问属性中的修饰符
// getModifiers()返回的是一个int类型的返回值,代表类、成员变量、方法的修饰符
String fieldModifier = Modifier.toString(instanceField.getModifiers());
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Java的类机制是指将类的字节码文件到内存中,并在运行时将其转换为可执行的代码的过程。Java的类机制遵循了一定的规则和顺序,可以分为以下几个步骤: 1. :类的第一步是,即将类的字节码文件到内存中。Java的类器负责从文件系统、网络或其他来源类的字节码文件。过程中会进行词法和语法的验证,确保字节码文件的正确性。 2. 链接:类的第二步是链接,即将已经的类与其他类或者符号进行关联。链接分为三个阶段: - 验证:验证阶段确保类的字节码文件符合Java虚拟机规范,包括检查文件格式、语义验证等。 - 准备:准备阶段为静态变量分配内存空间,并设置默认初始值。 - 解析:解析阶段将符号引用转换为直接引用,例如将类或者方法的符号引用解析为对应的内存地址。 3. 初始化:初始化是类的最后一步,在此步骤中会执行类的初始化代码,对静态变量进行赋值和执行静态代码块。类的初始化是在首次使用该类时触发的,或者通过反射方式调用`Class.forName()`方法来强制初始化。 Java的类机制是动态的,可以根据需要和卸类,它还支持类的继承、接口实现、内部类等特性。类机制Java语言的重要特性之一,它为Java提供了强大的动态性和灵活性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值