反射和类加载机制
一 类加载机制
1.1 加载机制简介
Java程序从编写到运行这个过程大致可以分为两个阶段:编译阶段和运行阶段。
编译阶段指的是,java源代码文件**(*.java
)被java编译器(javac)编译成字节码文件(*.class
)**的过程。这个过程不需要直接与硬件打交道,可以说,编译器无视操作系统,将源代码文件按照一套特定的规则将其翻译成一种特定的字节码形式。咱们这里不展开叙述了。
而当程序开始运行了,那么类的生命周期也就开始了。类的生命周期分为七个阶段,分别是加载,验证,准备,解析,初始化,使用,卸载。
阶段序号 | 阶段名称 | 说明 |
---|---|---|
阶段1 | 加载 | 将类的二进制数据加载到内存中,存放在方法区,并在堆区创建一个java.lang.Class对象,作为该类方法区数据的访问入口。 |
阶段2 | 验证 | 确保类的二进制数据符合JVM规范,包括字节码格式、变量和方法定义的有效性等。 A.class文件 |
阶段3 | 准备 | 为类的静态变量分配内存,并将其初始化为默认值。 |
阶段4 | 解析 | 将类、接口、字段和方法的符号引用转换为直接引用。 String a = “Helloworld” |
阶段5 | 初始化 | 执行类中的初始化代码块和静态初始化器。 |
阶段6 | 使用 | 类被应用程序使用。 |
阶段7 | 卸载 | 当类不再被使用且没有其他类引用时,从内存中卸载。 |
参考下图:
1.2 类的加载时机
Java类的加载时机是在该类首次被使用时,比如:
- 启动main方法时,main方法所在的类会被加载
- 调用new关键字来创建对象时
- 访问类的静态变量
- 访问类的静态方法
- 初始化子类时,父类也会被加载
- 使用
Class.forName("....")
反射机制时
1.3 类加载过程解析
类加载过程是由类加载器完成的,它将字节码(*.class)文件加载到JVM中。这个过程包括加载、链接(验证、准备、解析)和初始化三个小阶段
1)加载阶段
加载阶段是指从文件系统、网络或其他来源获取类的字节码文件,读到方法区,然后在堆内存创建Class对象的过程,具体分为以下三件事情:
- 通过类的全限定名来获取定义该类的二进制字节流,将字节码文件读进JVM的方法区中(jdk1.8以后为元空间Metaspace)
- 在方法区中将该字节流所代表的静态存储结构转化为运行时数据结构
- 在堆内存中生成一个代表这个类的 java.lang.Class对象,作为方法区中该类信息的访问入口。
2)链接阶段
整个链接阶段都是为了确保类的正确性和安全性,为类的后续使用提供了基础
-
验证
为了确保class文件的字节流包含的信息符合当前虚拟机的要求,不会危害虚拟机自身的安全。有文件格式验证、元数据验证、字节码验证、符号引用验证。
-
准备
JVM 会在该阶段对静态变量分配内存并进行默认初始化(不同数据类型会有其默认初始值,如:int ---- 0,boolean ---- false 等)。这些变量的内存空间会在方法区中分配。
public class ClassLoad {// 属性=成员变量=字段public int n1 = 10; //是实例属性, 不是静态变量,因此在准备阶段,是不会分配内存的public static int n2 = 20; //n2是静态变量,在该阶段 JVM 会为其分配内存,n2 默认初始化的值为0 ,而不是20public static final int n3 = 30; //n3 被 static final 修饰,是常量, 它和静态变量不一样, 其一旦赋值后值就不变,因此其默认初始化 n3 = 30}
-
解析
将符号引用转换为直接引用。符号引用是指通过字符串描述的类、字段、方法等,而直接引用则是内存地址或指针。
3)初始化阶段
该阶段会执行一个叫clinit的方法。该方法被叫做类构造器,由编译器按照源文件中的代码顺序,自动收集类的所有静态代码块和静态变量赋值语句合并生成的。
看下面例子:
public class ClassLoad {public static void main(String[] args) throws ClassNotFoundException {System.out.println(B.num);// 直接使用类的静态属性,也会导致类的加载}
}class B {static { // 静态代码块System.out.println("B 静态代码块被执行");num = 300;}static int num = 100;// 静态变量public B() {// 构造器System.out.println("B() 构造器被执行");}
}
输出结果:
B 静态代码块被执行
100
代码说明:
1.加载阶段: 加载 B类,并生成 B的 Class对象
2.连接阶段: 进行默认初始化 num = 0
3.初始化阶段: 执行 () 方法,该方法会依次自动收集类中的所有静态变量的赋值操作和静态代码块中的语句,并合并。如下:
clinit() {
System.out.println(“B 静态代码块被执行”);
num = 300;
num = 100;
}
注意:加载类的时候,具有同步机制控制。如下:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {//正因为有这个机制,才能保证某个类在内存中, 只有一份Class对象synchronized (getClassLoadingLock(name)) {//....}
}
1.4 类加载器说明
类的加载过程是由类加载器来完成的,类加载器根据一个类的全限定名读取类的二进制字节流到JVM中,然后生成对应的java.lang.Class对象实例
虚拟机默认提供了3种类加载器,启动类加载器(Bootstrap ClassLoader)、扩展类加载器(Extension ClassLoader)、应用类加载器(Application ClassLoader),如果有必要还可以加入自己定义的类加载器。
加载器类型 | 说明 |
---|---|
启动类加载器(Bootstrap ClassLoader) | 负责加载\lib目录 和 被-Xbootclasspath参数所指定的路径中的类库 |
扩展类加载器(Extension ClassLoader) | 负责加载\lib\ext目录 和 被java.ext.dirs系统变量所指定的路径中的所有类库 |
应用程序类加载器(Application ClassLoader) | 负责加载用户类路径classPath所指定的类库,如果应用程序中没有自定义过自己的类加载器,一般情况下这个就是程序中默认的类加载器。 |
自定义加载器(CustomClassLoader) | 由应用程序根据自身需要自定义,如 Tomcat、Jboss 都会根据 j2ee 规范自行实现。 |
任意一个类在 JVM 中的唯一性,是由加载它的类加载器和类的全限定名一共同确定的。因此,比较两个类是否“相等”的前提是这两个类是由同一个类加载器加载的,否则,即使两个类来源于同一个 Class 文件,被同一个虚拟机加载,只要加载他们的类加载器不同,那这两个类就必定不相等。
JVM 的类加载机制,规定一个类有且只有一个类加载器对它进行加载。而如何保证这个类只有一个类加载器对它进行加载呢?则是由双亲委派模型来实现的。
1.5 双亲委派模型
双亲委派模型要求除了顶层的启动类加载器外,其余类加载器都应该有自己的父类加载器。(类加载器之间的父子关系不是以继承的关系实现,而是使用组合关系来复用父加载器的代码)
工作原理
如果类加载器收到了类加载的请求,他首先不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成,每一个层级的类加载器都是如此,因此所有请求最终都会被传到最顶层的启动类加载器中,只有当父类加载器反馈自己无法完成这个加载请求时,子加载器才会尝试自己去加载。因此,加载过程可以看成自底向上检查类是否已经加载,然后自顶向下加载类。
双亲委派模型的优点
- 使用双亲委派模型来组织类加载器之间的关系,Java类随着它的类加载器一起具备了一种带有优先级的层次关系。
- 避免类的重复加载,当父类加载器已经加载了该类时,子类加载器就没必要再加载一次。
- 解决各个类加载器的基础类的统一问题,越基础的类由越上层的加载器进行加载。避免Java核心API中的类被随意替换,规避风险,防止核心API库被随意篡改。
例如类 java.lang.Object,它存在在 rt.jar 中,无论哪一个类加载器要加载这个类,最终都是委派给处于模型最顶端的 Bootstrap ClassLoader 进行加载,因此 Object 类在程序的各种类加载器环境中都是同一个类。相反,如果没有双亲委派模型而是由各个类加载器自行加载的话,如果用户编写了一个 java.lang.Object 的同名类并放在 ClassPath 中,那系统中将会出现多个不同的 Object 类,程序将混乱。因此,如果开发者尝试编写一个与 rt.jar 类库中重名的 Java 类,可以正常编译,但是永远无法被加载运行。
1.6 类加载器源码
实现双亲委派的代码都集中在 java.lang.ClassLoader 的 loadClass() 方法之中,下面我们简单看下 loadClass() 的源码是怎么样的:
public abstract class ClassLoader {// 每个类加载器都有一个父加载器private final ClassLoader parent;public Class<?> loadClass(String name) throws ClassNotFoundException {return loadClass(name, false);}protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{// 首先,检查该类是否已经加载过了Class<?> c = findLoadedClass(name);// 如果没有加载过if (c == null) {if (parent != null) {// 先委托给父加载器去加载,注意这是个递归调用c = parent.loadClass(name, false);} else {// 如果父加载器为空,查找 Bootstrap 加载器是不是加载过了c = findBootstrapClassOrNull(name);}// 如果父加载器没加载成功,调用自己的 findClass 去加载if (c == null) { c = findClass(name);}} return c;}// ClassLoader 中 findClass 方式需要被子类覆盖,下面这段代码就是对应代码protected Class<?> findClass(String name){//1. 根据传入的类名 name,到在特定目录下去寻找类文件,把 .class 文件读入内存//...//2. 调用 defineClass 将字节数组转成 Class 对象return defineClass(buf, off, len);}// 将字节码数组解析成一个 Class 对象,用 native 方法实现protected final Class<?> defineClass(byte[] b, int off, int len){//...}
}
二 反射机制简介
2.1 反射的概念
在上一章节中,我们在谈到类的加载时机时,提到过使用java的反射机制时,也会加载类。那么到底什么是反射机制,它能干什么,有哪些优缺点呢?
在这之前,你如何获取一个类的实例对象?是不是只学习了一种方式,使用new关键字来调用构造器获取实例。
而使用new关键字来获取实例,有如下优点:
- 1)性能高,JVM已经对这种调用进行了优化。
- 2)不需要额外的权限检查、直接调用构造器获取实例、简单方便
但是也有如下这些缺点:
- 1)new是在编译时创建对象,不能在运行时根据需要来动态的创建对象(编译时:指的是JIT即时编译期间,也就是将字节码翻译成本地机器码时)
- 2)new出来的对象只能访问公共成员,不能访问私有成员
- 3)程序运行时,new只能获取对象,不能获取到类的信息(类全名、属性、方法以及类型、修饰词等)
为了体现java语言的健壮性、以及更好的推广、Sun公司从JDK1.1版本开始、引入了反射机制这种高级特性,旨在提供一种在运行时动态的访问类和对象的技术。
下面来看一下Oracle 官方对反射的解释是:
Reflection enables Java code to discover information about the fields, methods and constructors of loaded classes, and to use reflected fields, methods, and constructors to operate on their underlying counterparts, within security restrictions. The API accommodates applications that need access to either the public members of a target object (based on its runtime class) or the members declared by a given class. It also allows programs to suppress default reflective access control.
简单来说,反射机制,就是让程序在运行时,根据需求,可以获取任意一个类的所有信息(私有的、公有的属性和方法),可以调用对象的任意属性和方法(包括私有的)。 而没有反射机制,程序要想获取这些信息,必须在运行前提前编译好相关信息。
2.2 运行时加载
我们再来看看下面的代码:
/*
* 编译期,有两个阶段,
* 1. 第一个阶段是*.java被翻译成*.class文件的时候,这个阶段我们称之为前端编译
* 2. 还有一个后端编译,指的是*.class文件加载到内存后,在真正运行前,需要将字节码翻译成本地机器码的过程。
* */
public class ClassLoad {public static void main(String[] args) throws ClassNotFoundException {Scanner sc = new Scanner(System.in);int key = sc.nextInt();switch(key) {case 0:// 普通代码: 后端编译时,编译到此处时,会加载Cat类的字节码文件到内存,// 进行各种验证工作。即使程序在运行时不走进case 0这个分支Cat cat = new Cat(); break;case 1:// 反射机制代码:后端编译时,编译到此处时,不会加载Dog这个类的Class文件,即使该字节码文件不存在,也不会报错。// 只有程序在运行时真正走到该行代码时,才会正式加载Dog类Class<?> dogClass = Class.forName("com.youcai.day04.Dog");// 注意:如果换成下列写法,Dog的字节码文件就必须在后端编译时,加载到内存中。无论是否走进case 1这个分支。Class<?> dogClass2 = Dog.class;break;}}
}
class Cat{}
从上面的案例中,我们可以发现:
- 编译时加载: 后端编译时加载相关的类,如果没有则报错,依赖性太强。如果程序不执行到该处,还占用内存资源。(也叫静态加载)
- 运行时加载: 运行时加载需要的类,如果运行时不用该类,则不报错,降低了依赖性,也不占用过多内存资源。(也叫动态加载)
而Java程序大多数时候都是编译时加载,也就是对象的类型在编译期就确定下来了,但是Java的反射机制不需要在编译期就确定类型,而是在真正使用时,去确定类型。
2.3 反射的用途(面试题)
那么,来总结一下反射都可以干什么?
- 运行时获取类的信息: 在许多情况下,我们需要在运行时获取类的信息,例如类的名称、修饰符、父类、接口等
- 运行时创建对象、调用方法: 这对于框架开发和插件系统非常有用(通过反射甚至可以调用private方法);
- 运行时访问和修改字段:反射甚至可以访问私有成员变量
- 动态代理:这是一种常用的设计模式。通过动态代理,我们可以在运行时生成代理对象,并在代理对象中添加额外的逻辑
java的反射,在很多框架和库中被广泛使用,例如Spring框架的依赖注入,Mybatis的resultType 对象等。相关API封装在java.lang.reflect包下。
2.4 反射与new区别
Pet p1 = new Pet("猫","花花",2); //直接初始化,[正射] [正向]
p1.eat("鱼骨头");//反射获取对象,调用方法
Class clz = Class.forName("com.se.day11.aClass.Pet");
Method method = clz.getMethod("setPrice", int.class);
Constructor constructor = clz.getConstructor();
Object object = constructor.newInstance();
method.invoke(object, 4);
两者都可以创建对象,但是他们之间有以下区别:
-
调用方式上
new直接找到构造器创建对象实例,简单直接。 反射通过java.lang.Class类的newInstance()方法或Constructor类的实例来创建对象。这种方式需要在运行时动态获取类的信息,然后通过反射机制创建对象。
-
创建对象的时机上
使用反射可以在运行时动态地创建对象,而使用new是在编译时创建对象。
-
性能上
反射的性能比使用new要差,因为在反射创建对象时需要进行类型检查和方法查找等操作,而使用new则不需要进行这些操作。
-
安全性上
直接通过new关键字创建对象,不需要额外的权限检查,比较安全。而反射需要更多的权限检查,且在运行时动态获取类信息,可能会引发安全问题。
-
应用场景上
new适用于所有需要创建对象的场景,是最常用的方式。而反射适用于需要动态创建对象的情况,例如在框架中动态加载类并创建对象,或者在测试中动态调用方法等。
2.4 反射的优缺点
优点
-
提高程序的灵活性:
-
反射允许在程序运行时获取和修改对象和类的信息,这使得程序更加动态和灵活,不需要硬编码。例如,即使是一个private的字段,通过反射也可以获取其值。
-
降低耦合性,提升复用率:
-
使用反射可以在运行时动态地确定类,降低了代码之间的耦合度,提高了代码的复用率。例如,动态代理技术就利用了反射来在运行时确定具体的类。
-
反射还可以用于实现插件架构和模块化设计,使得程序更加易于扩展和维护。
缺点:
-
性能不如直接调用:
反射需要动态加载类,获取类的信息,进行安全检查等额外开销,这些都会影响程序的性能。相比于直接调用,反射的效率较低。
-
代码可读性下降:
反射的动态性使得代码难以阅读和维护,缺乏类型检查,错误只能在运行时发现,增加了调试的难度。
-
可能破坏代码的抽象性:
反射可以绕过final等限制访问的属性或方法,可能会破坏代码的封装性和抽象性,影响代码的稳定性和安全性。
因此,如果不需要动态地创建一个对象,那么就别用反射;
三 Class类介绍
3.1 简要说明
Java语言中的任何类型在磁盘上都有一个独立*.class
字节码文件,该文件以一种特殊的数据结构来组织和描述一个类的所有信息。每当一个字节码文件被加载到JVM的内存中时,在堆内存中都会创建一个对象与之对应,用来描述该字节码文件里的所有信息。
举例说明
Person.class字节码文件-----> 堆中创建一个对象,描述信息可能是: 类名是Person, 三个属性,两个构造器,三个方法 Cat.class字节码文件--------> 堆中创建一个对象,描述信息可能是: 类名是Cat, 没有属性,一个构造器,一个方法 Employee.class字节码文件----> 堆中创建一个对象,描述信息可能是: 类名是Employee, 十个属性,四个构造器,六个方法 … 如果让你来给堆中的这些对象抽象出一个类型来,你会怎么做? 应该如下吧: class DescribeClass{ String nameClass, Field[] fieldInfo, Construct[] constructInfo, Method[] methodInfo, … }
而Java语言设计者,在最初时,就已经设计出来了相关类型,其中一个最重要的类型名,就叫Class,注意,开头字母是大写的C
Class是Java中的一个特殊的类型, 用来描述Java中比如每一个类、 接口、 枚举…编译后生成的.class字节码文件,里面封装的信息比如属性、构造方法、 方法等。这个类也是继承自Object类。
简单来说:Class类型就是用来描述Java中的其他任何类型。Class类型的一个实例就是对一个字节码文件的描述。
再思考一个问题
在堆内存中,会有一个Class类型的实例来描述Person.class这个字节码文件,也就是Person类型。那么有必要再创建一个Class类型的实例来描述Person类型吗?
没有必要,一个足够了。因此Class类型的实例都是单例模式的。
3.2 Class对象的获取
方式1:getClass方法
在Object类中, 有一个方法, 叫做 getClass()。 可以通过对象调用这个方法, 获取到一个用来描述这个对象所对应的类的Class信息。
public class ClassDemo01 {public static void main(String[] args) {//使用对象的getClass()方法来获取Class对象Person p1 = new Person();//Class 定义一个变量,指向了Person类型的类对象,每一个类都只且只有一个类型对象// Person只有一个类对象 Person.class Student类型也只有一个类对象Student.classClass<? extends Person> aClass = p1.getClass(); //获取了对应的唯一的类对象}public static class Person{}
}
方式2:class属性
可以使用类的属性 class
获取Class信息。
public class ClassDemo02 {public static void main(String[] args) {/*** 使用类的属性class,来获取本类的类对象。*/Class<Person> personClass = Person.class;System.out.println(personClass);}public static class Person{}
}
说明: 第一种方式和第二种方式,不能体现动态获取一个类对象,因为在使用普通类的时候内存里早就已经加载了普通类的字节码文件。
方式3:Class.forName方法(推荐)
由于在反射中,重点强调动态性。 类的获取、 属性的获取、 方法的获取、 构造方法的获取, 都是要通过一个字符串进行获取的, 我们甚至可以将一个需要加载的类名写到一个配置文件中, 在程序中读取这个配置文件中的数据, 加载不同的类。 这样一来, 当需要进行不同的类加载的时候, 直接改这个配置文件即可, 其他程序不用修改。 再例如, 可以通过属性的名字, 用反射获取到对应的属性, 并进行访问; 通过方法的名字, 用反射获取对应的方法, 并进行访问。 因此, 上述的两种Class对象的获取都不够动态, 我们需要用来获取Class信息, 更多的使用的就是这个方法。
这里会出现异常: ClassNotFoundException, 就是字面意思, 没有找到这个类, 很可能是因为类的名字写错了。 这里, 通过类的名字进行类的获取, 需要写类的全限定名, 即从最外层的包开始, 逐层向内查找, 直到找到这个类。 如果是内部类, 则需要用$进行向内查询。 参考内部类编译后生成的字节码文件的格式信息。
这个方法, 会将类加载到内存, 如果是第一次加载, 会触发这个类中的静态代码段。
public class ClassDemo03 {public static void main(String[] args) {/*** 使用Class.forName(String name)来获取一个类的类对象。* name: 是一个字符串,指的是一个类全名,* 比如下面的Person类型的 类全名:com.qf.reflect.ClassDemo03$Person*/try {Class<?> aClass = Class.forName("com.qf.reflect.Inter1");System.out.println(aClass);} catch (ClassNotFoundException e) {e.printStackTrace();}}public static class Person{}
}
interface Inter1{}
3.3 Class常用API
方法 | 解析 |
---|---|
static Class<?> forName(String className) | |
Class<?> getClass() | |
ClassLoader getClassLoader() | |
Field[] getFields() | |
Field getField(String name) | |
Field[] getDeclaredFields() | |
Field getDeclaredField(String name) | |
Constructor<?>[] getConstructors() | |
Constructor<T> getConstructor(Class<?>... parameterTypes) | |
Constructor<?>[] getDeclaredConstructors() | |
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) | |
Method[] getMethods() | |
Method getMethod(String name, Class<?>... parameterTypes) | |
Method[] getDeclaredMethods() | |
Method getDeclaredMethod(String name, Class<?>... parameterTypes) | |
T newInstance() |
3.3.1 查看属性
import java.lang.reflect.Field;
import java.lang.reflect.Method;/*** 获取成员变量*/
public class _01GetFieldDemo {public static void main(String[] args) {try{//获取描述类的对象Class<?> aClass = Class.forName(ReflectionUtil.getClassNameString());//获取所有的属性,包括私有的Field[] declaredFields = aClass.getDeclaredFields();for (Field declaredField : declaredFields) {//获取修饰词System.out.println("修饰词:"+declaredField.getModifiers());//获取属性类型System.out.println("属性类型:"+declaredField.getType());//获取属性名System.out.println("属性名:"+declaredField.getName());}}catch (Exception e){e.printStackTrace();}}
}
3.3.2 查看构造器
import java.lang.reflect.Constructor;
import java.lang.reflect.Parameter;/*** 获取所有的构造器,包括私有的** 修饰词对应的是常量:* public ---> 1* private ---->2* protected---->4* static---->8* final---->16* synchronized--->32*/
public class _01GetAllConstructorDemo {public static void main(String[] args) {try {//先获取描述类的对象,描述Person类型Class<?> aClass = Class.forName(ReflectionUtil.getClassNameString());//获取所有的构造器Constructor<?>[] declaredConstructors = aClass.getDeclaredConstructors();//遍历数组for (Constructor<?> constructor : declaredConstructors) {//获取构造器的修饰词System.out.println("构造器修饰词:"+constructor.getModifiers());//构造器的名字System.out.println("构造器名字:"+constructor.getName());//获取构造器的参数Parameter[] parameters = constructor.getParameters();for (Parameter parameter : parameters) {System.out.println("参数类型:"+parameter.getType());System.out.println("参数名称:"+parameter.getName());}System.out.println("----------------------------");}} catch (ClassNotFoundException e) {throw new RuntimeException(e);}}
}
3.3.3 查看方法
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;/*** 获取一个类中所有的方法,包括私有的*/
public class _02GetAllMethodDemo {public static void main(String[] args) {try{//获取描述Person类的类对象Class<?> aClass = Class.forName(ReflectionUtil.getClassNameString());//获取所有的方法Method[] methods = aClass.getDeclaredMethods();//遍历for (Method method : methods) {//方法的修饰词System.out.println("修饰词: " + method.getModifiers());//方法的返回值类型System.out.println("返回值类型:"+method.getReturnType());//方法名System.out.println("方法名:" + method.getName());//方法参数Parameter[] parameters = method.getParameters();for (Parameter parameter : parameters) {//参数类型System.out.println("参数类型:"+parameter.getType());}System.out.println("----------------------------");}}catch (Exception e){e.printStackTrace();}}
}
四 反射的基本操作
4.1 实例化对象
在反射中的对象实例化, 和之前面向对象部分不太一样。 在反射部分, 我们更多强调的是动态, 动态的进行一个类的对象实例化。 只需要通过一个类名字符串, 即可完成对这个对象的实例化。 很多时候, 我们可以将需要加载的不同的类, 以配置文件的形式写入到一个文件中, 可以使用程序读取这个文件, 从而得到一个类的名字, 通过反射进行不同的对象的实例化。
-
使用newInstance():
是Class类中的一个非静态的方法。 需要首先获取到Class对象, 使用这个Class对象进行方法的调用, 完成对象的实例化。
-
使用有权限的构造器
-
使用无权限的构造器
public class InstanceDemo01 {public static void main(String[] args) {/*** 获取com.qf.reflect.Person的类对象*/try {Class<?> aClass = Class.forName("com.qf.reflect.Person");/** 调用newInstance()来获取com.qf.reflect.Person类的对象* 此方法会调用类的无参构造器来获取对象** NoSuchMethodException:没有对应的无参构造方法*/Object o = aClass.newInstance();//System.out.println(o.getClass().getName());} catch (ClassNotFoundException | IllegalAccessException | InstantiationException e) {e.printStackTrace();}}
}
异常解析:
- InstantiationException: 类中没有无参构造方法, 有可能是因为在类中写了有参构造方法了。
- IllegalAccessException: 类中的无参构造方法的访问权限不足, 在类外无法进行访问。
4.1.2 方式2:指定构造方法
4.1.2.1 有权限的构造方法
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;public class InstanceDemo02 {public static void main(String[] args) throws ClassNotFoundException {//获取类对象try {Class<?> aClass = Class.forName(ReflectUtil.getStringfromFile());/*** Constructor getConstructor(Class .... parameters);* 通过制定参数的类对象以及顺序,来获取类的构造器**/Constructor<?> constructor = aClass.getConstructor(String.class, int.class);/***Object newInstance(Object .... paramenters)*** IllegalArgumentException: 调用的构造器的参数与实际参数的个数或者类型不匹配*/Object obj = constructor.newInstance("小明",23);System.out.println(obj);} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {e.printStackTrace();}}
}
4.1.2.2 无权限的构造方法
在反射中, 可以通过指定的方法, 获取到一些访问权限不足的成员。 例如, 可以通过 getDeclaredConstructor(Class...)
获取私有权限的构造方法。 但是, 即便获取到了访问权限不足的成员, 依然无法直接使用。 如果直接使用会出现 IllegalAccessException 异常, 表示访问权限不足。
此时, 可以使用 setAccessible 方法, 设置是否需要跳过权限的校验。
setAccessible(boolean flag) | 参数true: 代表关闭访问权限校验, 即任意的访问权限都可以访问。参数false: 代表不关闭访问权限校验, 此时在进行成员访问的时候, 依然需要进行权限的校验。 |
---|---|
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;public class InstanceDemo03 {public static void main(String[] args) throws ClassNotFoundException {//获取类对象try {Class<?> aClass = Class.forName(ReflectUtil.getStringfromFile());/*** Constructor getDeclaredConstructor(Class .... parameters);** 此方法可以获取任意一种构造方法,包括私有的**/Constructor<?> constructor = aClass.getDeclaredConstructor(String.class);/*** 调用setAccessable(boolean flag)方法,设置为true表示关闭权限的校验* false:开启校验*/constructor.setAccessible(true);/***Object newInstance(Object .... paramenters)* IllegalArgumentException: 调用的构造器的参数与实际参数的个数或者类型不匹配*/Object obj = constructor.newInstance("小明");System.out.println(obj);} catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {e.printStackTrace();}}
}
4.2 访问属性
- 访问有权限的非静态属性
- 访问有权限的非静态属性
- 访问静态属性
/*** 使用反射机制来访问属性*/
public class reflectDemo04 {public static void main(String[] args) {try {Class pClass = Class.forName("com.youcai.day10._06Reflect.Person");Constructor c1 = pClass.getDeclaredConstructor(String.class, int.class, char.class, double.class);Person o = (Person) c1.newInstance("小明", 17, '男', 18000);//获取成员属性 ageField ageFiled = pClass.getDeclaredField("age");//获取属性值:调用属性的get方法(Object o)Object o1 = ageFiled.get(o);System.out.println(o1);//修改属性值:调用属性的set方法(Object o,Object newValue)ageFiled.set(o,18);System.out.println(o);//访问静态属性:和对象没有关系,因此第一个参数为nullField countFiled = pClass.getDeclaredField("count");Object initValue = countFiled.get(null);System.out.println("默认值"+initValue);//修改静态属性值countFiled.set(null,100);System.out.println("修改后的值"+countFiled.get(null));System.out.println("----------------");Field maxAgeFiled = pClass.getDeclaredField("MAX_AGE");System.out.println("MAX_AGE:"+maxAgeFiled.get(null));}catch (Exception e){e.printStackTrace();}}
}
4.3 访问方法
- 访问有权限的无参方法
- 访问有权限的有参方法
- 访问有权限的静态方法
- 访问无权限的方法
/*** 使用反射机制访问方法* 1.成员方法* 2.静态方法*/
public class reflectDemo05 {public static void main(String[] args) {try {Class pClass = Class.forName("com.youcai.day10._06Reflect.Person");System.out.println("-------------成员方法的访问-------------");//获取对象Object o = pClass.newInstance();System.out.println("赋值前的样子:"+o);//获取setName()方法Method m1 = pClass.getDeclaredMethod("setName", String.class);//调用invoke方法m1.invoke(o,"张三");System.out.println("赋值后的样子:"+o);//获取私有的方法Method m2 = pClass.getDeclaredMethod("show", null);m2.setAccessible(true);m2.invoke(o);//获取带有返回值类型的方法Method m3 = pClass.getDeclaredMethod("getName");Object rValue = m3.invoke(o);System.out.println("返回值:"+rValue);System.out.println("-------------静态方法的访问-------------");Method m4 = pClass.getDeclaredMethod("getPI");Object sValue = m4.invoke(null);System.out.println("静态方法返回值:"+sValue);} catch (Exception e) {e.printStackTrace();}}
}