简介
当我们使用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