java反射机制的核心是在程序运行时启动动态加载并获取类的信息,从而操作类或对象的属性和方法,本质是jvm得到class对象后,再通过class对象进行反编译,从而获取对象的各种语言信息。
java属于先编译再执行的语言,程序中对象的类型在编译期就确定下来了,而当程序在运行时可能需要动态的加载某些类,这些类因为之前引用不到,所以没有被jvm加载。通过反射,可以在运行时动态的创建对象并调用其属性,不需要提前在编译期知道运行的对象是谁。
Class类
对象照镜子后可以得到的信息:某个类的属性、方法和构造器,某个类到底实现了哪些接口。对于每个类而言,JRE都为其保留了一个不变的Class类型的对象。一个Class对象包含了特定某个结构的相关信息。
- Class本身也是一个类
- Class对象只能由系统建立对象
- 一个加载的类在JVM中只会有一个Class实例
- 一个Class对象对应的是一个加载到JVM中的一个class文件
- 每个类的实例都会记得自己是由哪个Class实例所生成
- 通过Class可以完整的得到一个类中所有被加载的结构
- Class类是Reflection的根源,真对任何你想动态加载、运行的类、唯有先获得相应的Class对象
反射机制的常用类
- Java.lang.Class
- 代表一个类
- Java.lang.reflect.Constructor;
- 代表类的构造方法
- Java.lang.reflect.Field;
- 代表成员变量
- Java.lang.reflect.Method;
- 代表类的方法
- Java.lang.reflect.Modifier;
如何获得Class的三种方法
- 通过Class对象中的getClass来获取
- 任何数据类型,都有一个静态的class属性
- 通过class类的静态方法forName(String className)来获取
//第一种方法获取Class对象
Student stu1 = new Student(); //这个是new产生的Student对象,一个Class对象
Class stuClass = stu1.getClass();//获取class对象
System.out.println(stuClass.getName());
//第二种方法获取Class对象
Class stuClass2 = Student.class;
System.out.println(stuClass == stu2Class);
//获取父类
Class<?> superclass = stuClass2.getSuperclass();
//第三种方法获取class对象 (常用方法)
try{
Class stuClass3 = Class.forName(***反射的类) //代包名的路径和要反射的类
System.out.println(stuClass3 == stuClass2);
}catch(ClassNotFoundException e){
e.printStackTrace();
}
在运行期间,一个类只会产生一个Class对象
创建实例
使用Class对象的newInstance()方法来创建Class对象实例
Class<?> c = String.class;
Object str = c.newInstance();
先通过Class对象获取指定的Constructor(构造器)对象,再调用Constructor(构造器)对象的newInstance()方法来创建对象,这种方法可以指定的构造器构造实例类型
//通过String的Class对象
Class<?> str = String.class;
//通过Class对象获取指定的Constructor构造器对象
Constructor constructor = str.getConstructor(String.class);
//根据构造器来创建实例
Object obj = constructor.newInstance("Hello World");
通过反射获取构造方法并使用
批量获取的方法
public Constructor[] getConstructors():"所有公用的"构造方法
piblic Constructor[] getDeclaredConstructors():获取所有的构造方法
获取单个的方法,并调用
public Constructor getConstructor(Class... parameterTypes):获取单个的"共有的"构造方法
public Constructor getDeclaredConstructors(Class... parameterTypes):获取"某个构造方法"
调用构造方法
Constructor->newInstance(Object... initargs)
newInstance是 Constructor类的方法(管理构造函数的类)
构造方法的使用顺序
//1.加载Class对象
Class clazz = Class.forName("com.rzt.Student");
//2.创建构造方法
//因为是无参的构造方法
Constructor con = clazz.getConstructor(String.class,int.class);
//3.调用构造方法
Object obj = con.newInstance("男",20);
Java内存分析
Java内存
- 堆
- 存放new的内存对象和数组
- 可以被所有的线程共享,不会存放别的对象引用
- 栈
- 存放基本变量类型(会包含这个基本类型的具体数值)
- 引用对象的变量(会存放这个引用在堆里面的具体地址)
- 方法区
- 可以被所有线程共享
- 包含了所有class和static变量
当程序主动使某个类时,如果该类还未被加载到内存中,则系统会通过如下三个步骤来进行该类的初始化
- 类的加载(Load)
- 将类的class文件读入内存,并为之创建一个java.lang.Class对象。此过程由类加载器完成
- 类的链接(Link)
- 将类的二进制数组数据合并到JRE中
- 类的初始化
- JVM负责对类进行初始化
类的加载与ClassLoader的理解
- 加载
- 将class文件字节码内容加载到内存中,并将这些静态数据转换为方法区的运行时数据结构,然后生成一个代表这个类的java.lang.Class对象
- 链接
- 将java类的二进制代码合并到jvm的运行状态之中过程
- 验证:确保加载类信息符合jvm规范,没有安全方面问题(控制台报错)
- 准备:正式为类变量分配内存并设置类变量默认初始化值的阶段,这些内存都将在方法区中进行分配。
- 解析:虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程。
- 初始化
- 执行类构造器clinit方法的过程。类构造器clinit方法是由编译期自动收集类中所有类变量的赋值动作和静态代码块中的语句合并产生的。
- 当初始化一个类的时候,如果发现其父类方法还没有进行初始化,则需要先触发其父类的初始化。
- 虚拟机会保证一个类的clinit方法在多线程环境中被正确加锁和同步
什么时候会发生类初始化
- 类的主动引用(一定会发生类的初始化)
- 当虚拟机启动,先初始化main方法所在的类
- new一个类的对象
- 调用类的静态成员(除了final常量)和静态方法
- 使用java.lang.reflect包的方法对类进行反射调用
- 当初始化一个类,如果其父类没有被初始化,则先会初始化它的父类
- 类的被动引用(不好发生初始化)
- 当访问一个静态域时,只有真正的声明这个域的类才会被初始化。如:当通过子类引用父类的静态变量,不会导致类的初始化。
- 通过数组定义类引用,不会触发类的初始化
- 引用常量不会触发类的初始化(常量在连接阶段就存入调用类的常量池中了)
类加载器的作用
- 类加载的作用
- 将class文件字节码内容加载到内存中,并将这些静态数据转换成方法区的运行时数据结构,然后在堆中生成一个代表这个类的java.lang.Class对象,作为方法区中类数据的访问入口。
- 类缓存
- 标准的JavaSE类加载器可以安要求查找类,但是一旦某个类被加载代类加载器中,它将持续一段时间(缓存),不过JVM垃圾回收机制可以收回这些Class对象