类加载与反射

简介

当我们使用eclipse等IDE工具的时候,代码可以自动补全,这其中的原理是什么呢?
有没有什么方式在不改变Test类的情况下访问如下的私有变量呢?

public class Test {
    private int age;
    private String name;
}

下面将通过反射知识的学习进行以上问题的解决。
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
有所了解的可直接看文末代码。

类加载、连接和初始化

类的加载

Java作为一门面向对象的语言,一切都是对象(除了八个基本数据类型),因此类也是一种对象。系统中所有的类都是java.lang.Class的实例。
当程序主动使用某个类时,如果类尚未被加载到内存中,系统会对该类进行加载、连接、初始化操作。
类的加载由类加载器完成,类加载器通常由JVM提供。
通过使用不同的可加载器,可以从不同来源加载类的二进制数据,通常由如下几种:
- 从本地文件系统加载class文件,最常见的一种方式;
- 从JAR包加载class文件,比较常见;
- 通过网络加载class文件;
- 把一个Java源文件动态编译,并执行加载。

类的连接

当类别加载之后,系统位置生成一个对应的Class对象,接着会进入连接阶段:把类的二进制数据合并到JRE中。分为三个阶段:
- 验证:检验被加载的类是否有正确的内部结果;
- 准备:负责为类的类变量分配内存,并设置默认初始值;
- 解析:将类的二进制数据中的符号引用替换成直接引用。

类的初始化

类的初始化阶段,虚拟机负责对类进行初始化,主要是对类变量进行初始化。类变量制定初始化值有两种方式:①声明类变量是指定初始值;②使用静态初始化块为类变量制定初始化值。
JVM初始化类包含以下几个步骤:
- 假如这个类还没有被加载和连接,则程序先加载并连接该类;
- 假如该类的直接父类尚未被初始化,则先初始化其直接父类;
- 假如类中有初始化语句,则系统一次执行这些初始化语句。
类初始化时机
当Java程序首次通过以下六种方式使用某个类或接口时,系统会初始化该类或接口:
- 创建类的实例。包括:使用new操作符创建实例;通过反射创建实例;通过反序列化创建实例(?)。
- 调用某个类的类方法。
- 访问某个类或接口的类变量,或为该类变量赋值。
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象。
- 初始化某个类的子类,
- 直接使用java.exe命令运行某个主类。

类加载器

类加载器负责将.class文件加载到内存中,并为之生成java.lang.Class对象。
当JVM启动时,会形成由三个类加载器组成的初始类加载器层次结构。
- Bootstrap ClassLoader:根类加载器。负责加载Java核心类。
- Extension ClassLoader:扩展类加载器。负责加载JRE扩展目录(C:\Program Files\Java\jre1.8.0_131\lib\ext)中JAR包的类。
- System ClassLoader:系统类加载器。负责在JVM启动时加载来自java命令的-classpath选项、java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。

通过反射查看类信息

Java程序中许多对象在运行时会出现两种类型:编译时类型和运行时类型。如

        Person person = new Student();

以上代码将会生成变量person,该变量的编译时类型为Person,运行时类型为Student。程序在运行时发现对象和类的真实信息,有如下两种方式:

  • 在编译时和运行时完全知道类型的具体信息。可以先使用instanceof运算符进行判断,再利用强制类型转换将其转换成其运行时类型的变量。如下列:
        Object obj = "arvin";
        if (obj instanceof String) {
            String str = (String) obj;

        }
  • 编译时无法预知该对象和类可能属于哪些类,程序只能依靠运行时信息发现类的真实信息,则需要使用反射。

获取Class对象

类在加载后。系统回味该类生成一个对象的Class对象,通过该对象可以访问JVM中的这个类。在Java程序中有如下三种方式获取Class对象:
- 使用Class类的forName(String clazzName)静态方法。传入的clazzName参数值必须使某个类的全限定类名(包括完整包名)。
- 调用某个类的class属性来获取该类对应的Class对象。如:Person.class将会返回Person类对象的Class对象。
- 调用某个对象的getClass()方法。
对于第一种方式和第二种方式都是直接根据类返回所对应的Class对象,第二种方式有如下两个优势:
- 代码更安全。代码在编译阶段就可以检查需要刚问的Class对象是否存在。
- 程序性能更好。因为无需调用方法。
因而大部分时候使用第二种方式获取指定类的Class对象。但如果程序只能获得一个字符串,则只能使用第一种方式。
一旦获得某个类所对应的Class对象之后,程序就可以调用Class对象的方法来获得该对象的信息。

从Class中获取信息

Class类提供了大量的实例方法来获取该Class对象所对应类的详细信息。详细信息可查阅API文档,下面仅列出常用方法。

构造器
  • Constructor< T> getConstructor(Class< ?>…parameterTypes):返回此Class对象对应类的、带指定形参列表的public构造器。
  • Constructor< ?>[] getConstructors():返回此Class对象对应类的所有public构造器。
  • Constructor< T> getDeclaredConstructor(Class< ?>…parameterTypes):返回此Class对象对应类的、带指定形参列表的构造器,与访问权限无关。
  • Constructor< ?>[] getDeclaredConstructors():返回此Class对象对应类的所有构造器,与访问权限无关。
方法
  • Method getMethod(String name, Class<>…parameterTypes):返回此Class对象对应的、带指定形参列表的public方法。
  • Method[] getMethods():返回此Class对象所表示的类的所有public方法。
  • Method getDeclaredMethod(String name, Class<>…parameterTypes):返回此Class对象对应的、带指定形参列表的所有方法。
  • Method[] getDeclaredMethods():返回此Class对象所表示的类的所有方法。
成员变量
  • Field getField(String name):返回此Class对象对应类的、指定名称的public成员变量。
  • Field[] getFields():返回此Class对象对应类的所有的public成员变量。
  • Field getDeclaredField(String name):返回此Class对象对应类的、指定名称的成员变量,与成员变量的访问权限无关。
  • Field[] getDeclaredFields():返回此Class对象对应类的所有的成员变量。
Annotation
  • < A extends Annotation> A getAnnotation(Class< A> annotationClass):尝试获取该Class对象对应类上存在的、指定类型的Annotation;如果该类型的注解不存在,则返回null。
  • < A extends Annotation> A getDeclaredAnnotation(Class< A> annotationClass):尝试获取直接修饰该Class对象对应类的、指定类型的Annotation;如果该类型的注解不存在,则返回null。
  • Annotation[] getAnnotations():返回修饰该Class对象对应类上存在的所有Annotation。
  • Annotation[] getDeclaredAnnotations():返回直接修饰该Class对应类的所有Annotation。

使用反射生成并操作对象

创建对象

通过反射生成对象有两种方式
- 使用Class对象的newInstance()方法来创建该Class对象对应类的实例,要求该Class对象的对应类有默认构造器。
- 使用Class对象获取制定Constructor对象,在调用Constructor对象的newInstance()方法创建Class对象的对应类的实例,该方式可选择使用制定的构造器来创建实例。

调用方法
  • Object invoke(Object obj, Object…args): 该方法中的obj是执行该方法的主调,args是实参。
访问成员变量值
  • getXxx(Object obj): 获取obj对象的该成员变量的值。此处的Xxx对应8种基本类型,如果该成员变量的类型是引用类型,则取消get后面的Xxx。
  • setXxx(Object obj, Xxx val): 将obj对象的该成员变量设置成val值。此处的Xxx对应8中基本类型,如果该成员变量的类型是引用类型,则取消set后面的Xxx。

举例代码

package day608;

import java.lang.reflect.Field;

class Test {
    private int age;
    private String name;

    void intro() {
        System.out.println("年龄是:" + age + "\n" + "姓名是:" + name);
    }
}

public class ClassTest {
    public static void main(String[] args)
            throws NoSuchFieldException, SecurityException, IllegalArgumentException, IllegalAccessException {
        // 创建实例
        Test test = new Test();
        // 以姓名为例
        String name = "name";
        // 获得Class对象
        Class<Test> class1 = Test.class;
        // 获取成员变量,与访问权限无关
        Field fieldName = class1.getDeclaredField(name);
        // 设置检查属性:无需检查访问修饰符
        fieldName.setAccessible(true);
        // 设置变量
        fieldName.set(test, "zhangsan");

        test.intro();
    }
}

控制台输出结果

年龄是:0
姓名是:zhangsan
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值