动态语言:运行是代码可以根据某些条件改变自身结构,像js和php,python等,但是我们不像c++,是一门静态语言,可以准确的说我们是一门准动态语言,因为反射让我们具有动态性。
我来直接用我所理解的反射给大家先讲一下大概(这绝对让你的耳目一新,不会私信过来我报位置你来刀我):
反射:将类的各个组成部分封装为对象,这就是反射所具有的机制
我们从基层聊起:
写Java代码我们所需要经历三个阶段:
源代码阶段:首先我们会先写一个基础代码,通过javac编译就会在我们的硬盘上生成一个字节码文件(成员变量,构造方法,成员方法等),这个阶段代码还在硬盘没有进内存
Class类阶段:通过类加载器(对应着Java中的对象Classloader)将Java的字节文件加载到内存里面,然而在Java中万物皆对象(所以你也是有对象的)而这个对象便是Class 类对象(用来描述所以的字节码文件)而在这个Class对象也有三部分比较重要的东西:成员变量 封装为 Field[ ]fielids 构造方法封装为 Constructor[ ] con 成员方法封装为 Metod[ ] mets(因为不可能只有一个成员变量(构造方法,成员方法),因此使用数组描述所以成员变量(构造方法,成员方法)),然后就可以去创建其他对象
Runtime运行阶段:就是我们一直所说的new一个对象
现在我们说一下我们将成员变量 封装为 Field
构造方法封装为 Constructor
成员方法封装为 Metod
这个就是我们所说的反射机制。
是不是感觉有那么一点思路了,我们继续深究一下便好
目录
1.类加载
1.1类加载
类的加载详细过程
什么时候会发生类的初始化
1.2类加载器
2.反射(框架设计的灵魂)
1.1 反射的作用:
1.2 反射的概述
1.3 获取class类的对象:
1.4Class对象的分类
1.4.1反射_获取Constructor构造器对象.
1.4.2 反射_获取Field成员变量对象。
1.4.3 获取Method方法对象
作者最后的挣扎:
1.类加载
理解类加载是理解反射最快的方法
1.1类加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤来对类进行初始化。如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化
类的加载详细过程
- 类的加载:就是把类的class文件读到内存中,并为其创建class对象。(类加载器来完成)
- 类的链接:将Java类的二进制数据合并到JVM的运行状态之中的过程。
- 验证:确保加载的类信息符合JVM规范,没有安全方面的问题。
- 准备:正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区中进行分配。
- 解析:JVM常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 类的初始化:JVM负责对类进行初始化
- 执行类构造器的<clinit>()方法的过程。
这个方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的(类构造器是构造类信息的,不是构造该类对象的构造器)
- 当初始化一个类的时候,如果发现其父类还没有进行初始化,则需要先触发其父类的初始化。
- JVM会保证一个类的<clinit>()方法在多线程环境中被正确加锁和同步
类的初始化步骤
●假如类还未被加载和连接,则程序先加载并连接该类
●假如该类的直接父类还未被初始化, 则先初始化其直接父类
●假如类中有初始化语句, 则系统依次执行这些初始化语句
注意:在执行第2个步骤的时候,系统对直接父类的初始化步骤也遵循初始化步骤1-3
什么时候会发生类的初始化
-
类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类。
- new一个类的对象。
- 调用类的静态成员(除了final常量)和静态方法。
- 使用java.lang.reflect包的方法对类进行反射调用。
- 当初始化一个类,如果其父类没有被初始化,则先会初始化他的父类。
-
类的被动引用(不会发生类的初始化)
- 当访问一个静态域时,只有真正声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致子类初始化。
- 通过数组定义类引用,不会触发此类的初始化。
- 引用常量不会触发此类的初始化(常量在链接阶段就存入调用类的常量池中了)。
package 反射;
public class Texy02 {
public static void main(String[] args) {
hong h=new hong() ;
System.out.println(hong.m);}
}
class hong{
static {
System.out.println("hong");
m=300;
}
static int m=100;
public hong(){
System.out.println("hong1");
}
}
解释原理:(Class对象是在类加载时候产生的)
先从方法区存放某个类的值,随后放到堆里面的java.lang.Class对象来代表某个类,开始看栈里面的main方法,开始链接并执行代码,new某类的时候会产生某类的对象,对象就会去寻找自己的Class类,并会获得数据
1.2类加载器
1.2.1 类加载器的作用
●负责将.class文件加载到内存中 ,并为之生成对应的java.lang.Class对象
●虽然我们不用过分关心类加载机制,但是了解这个机制我们就能更好的理解程序的运行
1.2.2 JVM的类加载机制
●全盘负责:就是当一一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
●父类委托:就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
●缓存机制: 保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时, 类载器先从缓存区中搜索该Class,只有当缓存区中不存在该Cass对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存储到缓存区
ClassLoader:负责加载类的对象
1.2.3 Java运行时具有以下内置类加载器
●Bootstrap class loader:它是虚拟机的内置类加载器,通常表示为null ,并且没有父null
●Platformclass loader:平台类加载器可以看到所有平台类,平台类包括由平台类加载器或其祖先定义的Java SE平台API,其实现类和JDK特定的运行时类
●System class loader:它也被称为应用程序类加载器,与平台类加载器不同。系统类加载器通常用于定义应用程序类路径,模块路径和JDK特定工具上的类
●类加载器的继承关系: System的父加载器为Platform, 而Platform的父加载器为Bootstrap
ClassLoader中的两个方法
static Classl oader getSystemClassLoader):返回用于委派的系统类加载器
ClassLoader getParent0:返回父类加载器进行委派
package 反射;
public class Text03 {
public static void main(String[] args) throws ClassNotFoundException {
// 获取系统类的加载器
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
System.out.println(systemClassLoader);// 打印:sun.misc.Launcher$AppClassLoader@18b4aac2
// 获取系统类加载器的父类加载器-->扩展类加载器
ClassLoader parent = systemClassLoader.getParent();
System.out.println(parent);// 打印:sun.misc.Launcher$ExtClassLoader@1b6d3586
// 获取扩展类加载器的父类加载器-->根加载器(C/C++) 根加载器无法直接获取
ClassLoader parent1 = parent.getParent();
System.out.println(parent1);// 打印:null
// 测试当前类是哪个加载器加载的
ClassLoader classLoader = Class.forName("反射.Text03").getClassLoader();
System.out.println(classLoader);// 打印:sun.misc.Launcher$AppClassLoader@18b4aac2
// 测试jdk内部类是哪个加载器加载的。(由根加载器加载的,所以打印不出来,为null)
ClassLoader classLoader1 = Class.forName("java.lang.Object").getClassLoader();
System.out.println(classLoader1);// 打印:null
// 如何获得系统类加载器可以加载的路径
System.out.println(System.getProperty("java.class.path"));
// 打印:
}
}
2.反射(框架设计的灵魂)
1.1 反射的作用:
在java运行环境中,对于任意一个类,可以知道这个类的所有属性和方法,对于任意一个对象,可以调用它的任意属性和方法,这种动态获取类的信息以及动态调用对象方法的功能,就来自于java语言的反射机制。反射是java提供的一个重要功能,可以在运行时检查、类、接口、方法和变量等信息,无需知道类的方法和方法名等,还可以在运行时实例化新对象、调用方法以及设置和获取变量值。
1.2 反射的概述
- 反射是指对于任何一个Class类,在"运行的时候"都可以直接得到这个类全部成分。
- 在运行时,可以直接得到这个类的构造器对象:Constructor
- 在运行时,可以直接得到这个类的成员变量对象:Field
- 在运行时,可以直接得到这个类的成员方法对象:Method
- 这种运行时动态获取类信息以及动态调用类中成分的能力称为Java语言的反射机制。
class类就是所以.class所对应的类型,就是如同上图所表示的音像或者躯体,然后就可以通过class来使用成员变量,构造方法的,就不在使用student和teacher,这个原理就是反射。
1.3 获取class类的对象:
哪些类型可以有Class对象?
➢class: 外部类,成员(成员内部类,静态内部类),局部内部类,匿名内部类。
➢interface: 接口
➢[]:数组
➢enum:枚举
➢annotation: 注解@interface
➢primitive type:基本数据类型
➢void
我们要想通过反射去使用一个类,首先我们要获取该类的字节码文件对象,也就是类型为class类型的对象。
➢Class 本身也是一个类
➢Class 对象只能由系统建立对象
➢一个加载的类在JVM中只会有一个Class实例
➢一个Class对象对应的是一 个加载到JVM中的一个.class文件
➢每个类的实例都会记得自己是由哪个Class实例所生成
➢通过Class可以完整地得到一个类中的所有被加载的结构
➢Class类 是Reflection的根源,针对任何你想动态加载、运行的类,唯有先获得相应的
Class对象
反射的第一步是什么?
获取Class类对象,如此才可以解析类的全部成分
获取Class类的对象的三种方式
方式一:Class c1 = Class.forName(“全类名”);
方式二:Class c2 = 类名.class
方式三:Class c3 = 对象.getClass();
package 反射;
public class Text01 {
public static void main(String[] args) throws ClassNotFoundException {
// 1、Class类中的一个静态方法:forName(全限名:包名 + 类名)
Class c = Class.forName("反射.Text01");
System.out.println(c); // Student.class
// 2、类名.class
Class c1 = Student.class;
System.out.println(c1);
// 3、对象.getClass() 获取对象对应类的Class对象。
Student s = new Student();
Class c2 = s.getClass();
System.out.println(c2);
}
}
class Student {
private String name;
private int age;
Student(){
System.out.println("无参数构造器执行!");
}
public Student(String name, int age) {
System.out.println("有参数构造器执行!");
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
1.4Class对象的分类
1.4.1反射_获取Constructor构造器对象.
反射的第一步是先得到Class类对象。(Class文件)
反射中Class类型获取构造器提供了很多的API:
1. Constructor getConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只能拿public修饰的构造器,几乎不用!
2. Constructor getDeclaredConstructor(Class... parameterTypes)
根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
3. Constructor[] getConstructors()
获取所有的构造器,只能拿public修饰的构造器。几乎不用!!太弱了!
4. Constructor[] getDeclaredConstructors()
获取所有申明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
小结:
获取类的全部构造器对象: Constructor[] getDeclaredConstructors()
-- 获取所有申明的构造器,只要你写我就能拿到,无所谓权限。建议使用!!
获取类的某个构造器对象:Constructor getDeclaredConstructor(Class... parameterTypes)
-- 根据参数匹配获取某个构造器,只要申明就可以定位,不关心权限修饰符,建议使用!
package 反射;
import org.junit.Test;
import java.lang.reflect.Constructor;
/**
目标: 反射_获取Constructor构造器然后通过这个构造器初始化对象。
反射获取Class中的构造器对象Constructor作用:
也是初始化并得到类的一个对象返回。
Constructor的API:
1. T newInstance(Object... initargs)
创建对象,注入构造器需要的数据。
2. void setAccessible(true)
修改访问权限,true代表暴力攻破权限,false表示保留不可访问权限(暴力反射)
小结:
可以通过定位类的构造器对象。
如果构造器对象没有访问权限可以通过:void setAccessible(true)打开权限
构造器可以通过T newInstance(Object... initargs)调用自己,传入参数!
*/
public class Test04 {
// 1.调用构造器得到一个类的对象返回。
@Test
public void getDeclaredConstructor() throws Exception {
// a.第一步:获取类对象
Class c = Student.class;
// b.定位单个构造器对象 (按照参数定位无参数构造器)
Constructor cons = c.getDeclaredConstructor();
System.out.println(cons.getName() + "===>" + cons.getParameterCount());
// 如果遇到了私有的构造器,可以暴力反射
cons.setAccessible(true); // 权限被打开
Student s = (Student) cons.newInstance();
System.out.println(s);
System.out.println("-------------------");
// c.定位某个有参构造器
Constructor cons1 = c.getDeclaredConstructor(String.class, int.class);
System.out.println(cons1.getName() + "===>" + cons1.getParameterCount());
Student s1 = (Student) cons1.newInstance("孙悟空", 1000);
System.out.println(s1);
}
}
1.4.2 反射_获取Field成员变量对象。
反射的第一步是先得到Class类对象。
1、Field getField(String name);
根据成员变量名获得对应Field对象,只能获得public修饰
2.Field getDeclaredField(String name);
根据成员变量名获得对应Field对象,只要申明了就可以得到
3.Field[] getFields();
获得所有的成员变量对应的Field对象,只能获得public的
4.Field[] getDeclaredFields();
获得所有的成员变量对应的Field对象,只要申明了就可以得到
小结:
获取全部成员变量:getDeclaredFields
获取某个成员变量:getDeclaredField
反射获取成员变量: 取值和赋值。
Field的方法:给成员变量赋值和取值
void set(Object obj, Object value):给对象注入某个成员变量数据
Object get(Object obj):获取对象的成员变量的值。
void setAccessible(true);暴力反射,设置为可以直接访问私有类型的属性。
Class getType(); 获取属性的类型,返回Class对象。
String getName(); 获取属性的名称。
1.4.3 获取Method方法对象
反射获取类的Method方法对象:
1、Method getMethod(String name,Class...args);
根据方法名和参数类型获得对应的方法对象,只能获得public的
2、Method getDeclaredMethod(String name,Class...args);
根据方法名和参数类型获得对应的方法对象,包括private的
3、Method[] getMethods();
获得类中的所有成员方法对象,返回数组,只能获得public修饰的且包含父类的
4、Method[] getDeclaredMethods();
获得类中的所有成员方法对象,返回数组,只获得本类申明的方法。
Method的方法执行:
Object invoke(Object obj, Object... args)
参数一:触发的是哪个对象的方法执行。
参数二: args:调用方法时传递的实际参数
作者最后的挣扎:
现在是不是已经差不多了,要是还没有理解恭喜你,你没有理解的那一块我可能也不会,实力可能有限,可以找找视频或者找一个前辈,在项目中试试就差不多了