反射
1.类加载的机制的层次结构
每个编写的".java"拓展名类文件都存储着需要执行的程序逻辑,这些".java"文件经过Java编译器编译成拓展名为".class"的文件,“.class"文件中保存着Java代码经转换后的虚拟机指令,当需要使用某个类时,虚拟机将会加载它的”.class"文件,并创建对应的class对象,将class文件加载到虚拟机的内存,这个过程称为类加载,这里我们需要了解一下类加载的过程,如下:
1.1加载:
类加载过程的一个阶段:通过一个类的完全限定查找此类字节码文件,并利用字节码文件创建一个Class对象
1.2验证:
目的在于确保Class文件的字节流中包含信息符合当前虚拟机要求,不会危害虚拟机自身安全。主要包括四种验证,文件格式验证,元数据验证,字节码验证,符号引用验证。
1.3准备:
为类变量(即static修饰的字段变量)分配内存并且设置该类变量的初始值即0(如static int i=5;这里只将i初始化为0,至于5的值将在初始化时赋值),这里不包含用final修饰的static,因为final在编译的时候就会分配了,注意这里不会为实例变量分配初始化,类变量会分配在方法区中,而实例变量是会随着对象一起分配到Java堆中。
1.4解析:
主要将常量池中的符号引用替换为直接引用的过程。符号引用就是一组符号来描述目标,可以是任何字面量,而直接引用就是直接指向目标的指针、相对偏移量或一个间接定位到目标的句柄。有类或接口的解析,字段解析,类方法解析,接口方法解析(这里涉及到字节码变量的引用,如需更详细了解,可参考《深入Java虚拟机》)。
1.5初始化:
类加载最后阶段,若该类具有超类,则对其进行初始化,执行静态初始化器和静态初始化成员变量(如前面只初始化了默认值的static变量将会在这个阶段赋值,成员变量也将被初始化)。
2.类加载器
这便是类加载的5个过程,而类加载器的任务是根据一个类的全限定名来读取此类的二进制字节流到JVM中,然后转换为一个与目标类对应的java.lang.Class对象实例,在虚拟机提供了3种类加载器,引导(Bootstrap)类加载器、扩展(Extension)类加载器、系统(System)类加载器(也称应用类加载器),下面分别介绍
2.1 启动(Bootstrap)类加载器
启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C++语言实现的,是虚拟机自身的一部分,它负责将 <JAVA_HOME>/lib路径下的核心类库或-Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机识别,即使把jar包丢到lib目录下也是没有作用的(出于安全考虑,Bootstrap启动类加载器只加载包名为java、javax、sun等开头的类)。
2.2 扩展(Extension)类加载器
扩展类加载器是指Sun公司(已被Oracle收购)实现的sun.misc.Launcher$ExtClassLoader类,由Java语言实现的,是Launcher的静态内部类,它负责加载<JAVA_HOME>/lib/ext目录下或者由系统变量-Djava.ext.dir指定位路径中的类库,开发者可以直接使用标准扩展类加载器。
2.3系统(System)类加载器
也称应用程序加载器是指 Sun公司实现的sun.misc.Launcher$AppClassLoader。它负责加载系统类路径java -classpath或-D java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器,一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。
举例:
@Test
public void test01() {
ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader();
// app 系统类加载器名字
System.out.println(systemClassLoader.getName());
// platform 扩展类加载器名字
System.out.println(systemClassLoader.getParent().getName());
// java.lang.NullPointerException: Cannot invoke "java.lang.ClassLoader.getName()" because the return value of "java.lang.ClassLoader.getParent()" is null 启用类加载器 看不到权限等级太高
System.out.println(systemClassLoader.getParent().getParent().getName());
}
2.4 类加载器间的关系
我们进一步了解类加载器间的关系(并非指继承关系),主要可以分为以下4点
启动类加载器,由C++实现,没有父类。
拓展类加载器(ExtClassLoader),由Java语言实现,父类加载器为null
系统类加载器(AppClassLoader),由Java语言实现,父类加载器为ExtClassLoader
自定义类加载器,父类加载器肯定为AppClassLoader。
3.双亲委派模式
1.1双亲委派模式工作原理
双亲委派模式要求除了顶层的启动类加载器外,其余的类加载器都应当有自己的父类加载器,请注意双亲委派模式中的父子关系并非通常所说的类继承关系,而是采用组合关系来复用父类加载器的相关代码,类加载器间的关系如下:
双亲委派模式是在Java 1.2后引入的,其工作原理的是,如果一个类加载器收到了类加载请求,它并不会自己先去加载,而是把这个请求委托给父类的加载器去执行,如果父类加载器还存在其父类加载器,则进一步向上委托,依次递归,请求最终将到达顶层的启动类加载器,如果父类加载器可以完成类加载任务,就成功返回,倘若父类加载器无法完成此加载任务,子加载器才会尝试自己去加载,这就是双亲委派模式,即每个儿子都很懒,每次有活就丢给父亲去干,直到父亲说这件事我也干不了时,儿子自己想办法去完成,这不就是传说中的实力坑爹啊?那么采用这种模式有啥用呢?
1.2双亲委派模式优势
采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。其次是考虑到安全因素,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。可能你会想,如果我们在classpath路径下自定义一个名为java.lang.SingleInterge类(该类是胡编的)呢?该类并不存在java.lang中,经过双亲委托模式,传递到启动类加载器中,由于父类加载器路径下并没有该类,所以不会加载,将反向委托给子类加载器加载,最终会通过系统类加载器加载该类。但是这样做是不允许,因为java.lang是核心API包,需要访问权限,强制加载将会报出如下异常
java.lang.SecurityException: Prohibited package name: java.lang
4.类的加载5个方法
4.1 类名.class
@Test
public void test02() {
Class<Emp> empClass = Emp.class;
// class com.lihaozhe.bean.Emp
System.out.println(empClass);
// com.lihaozhe.bean.Emp
System.out.println(empClass.getName());
// Emp
System.out.println(empClass.getSimpleName());
}
4.2 对象名.class
@Test
public void test03() {
Emp emp = new Emp();
Class<? extends Emp> empClass = emp.getClass();
// class com.lihaozhe.bean.Emp
System.out.println(empClass);
// com.lihaozhe.bean.Emp
System.out.println(empClass.getName());
// Emp
System.out.println(empClass.getSimpleName());
}
4.3 class.forName(类的完全限定即包名+类名)
@Test
public void test04() {
try {
Class<?> empClass = Class.forName("com.lihaozhe.bean.Emp");
// class com.lihaozhe.bean.Emp
System.out.println(empClass);
// com.lihaozhe.bean.Emp
System.out.println(empClass.getName());
// Emp
System.out.println(empClass.getSimpleName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
4.4 使用一个类的类加载器加载另一个类
@Test
public void test05() {
ClassLoader classLoader = Person.class.getClassLoader();
try {
Class<?> empClass = classLoader.loadClass("com.lihaozhe.bean.Emp");
// class com.lihaozhe.bean.Emp
System.out.println(empClass);
// com.lihaozhe.bean.Emp
System.out.println(empClass.getName());
// Emp
System.out.println(empClass.getSimpleName());
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
4.5 使用包装类
@Test
public void test06() {
Class<Integer> type = Integer.TYPE;
// int
System.out.println(type);
// int
System.out.println(type.getName());
// int
System.out.println(type.getSimpleName());
}
5.反射实际操作
5.1利用反射实例化对象empClass.newInstance()
//1.获取该类的class对象
Class<Emp> empClass = Emp.class;
try {
//2.使用该类的class对象将该类实例化 (java8之前) 上面有横线 说明这个写法已经过时了
Emp emp = empClass.newInstance();
emp.setName("张益鸣");
System.out.println(emp.getName());
} catch (InstantiationException | IllegalAccessException e) {
e.printStackTrace();
}
5.2 获取class对象的构造方法empClass.getConstructor
//1.获取该类的class对象
Class<Emp> empClass = Emp.class;
try {
//2.获取该class对象的构造方法
Constructor<Emp> constructor = empClass.getConstructor();
//3.使用该类的构造方法将该类实例化(java8之前)
Emp emp = constructor.newInstance();
emp.setName("张益鸣");
System.out.println(emp.getName());
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) {
e.printStackTrace();
}
5.3 利用有参构造方法实例化对象empClass.getConstructor(String.class, Integer.class, Integer.class);
//利用有参构造方法实例化对象
//1.获取该类的class对象
Class<Emp> empClass = Emp.class;
try {
//2.获取该class对象的构造方法 并传入参数数据类型.class
Constructor<Emp> constructor = empClass.getConstructor(String.class, Integer.class, Integer.class);
//3.使用该类的构造方法将该类实例化
Emp emp = constructor.newInstance("张益鸣", 23, 1);
System.out.println(emp.toString());
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
5.4 执行静态方法empClass.getMethod
//执行静态方法
//1.获取该类的class对象
Class<Emp> empClass = Emp.class;
try {
// 2、根据方法名字获取该类中的方法
Method method = empClass.getMethod("haha");
// 3、执行该类中的方法
method.invoke(empClass);
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
5.5 执行非静态方法 method.invoke(empClass);
//执行非静态方法
//1.获取该类的class对象
Class<Emp> empClass = Emp.class;
try {
//2.根据方法名字获取该类中的方法
Method method = empClass.getMethod("heihei");
//3.获取该class对象的构造方法
Constructor<Emp> constructor = empClass.getConstructor();
//4.使用该类的构造方法将该类实例化
Emp emp = constructor.newInstance();
//5.执行该类中的方法
method.invoke(emp);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
5.6 执行private修饰的非静态方法 empClass.getDeclaredMethod
//执行private修饰的非静态方法
//1.获取该类的class对象
Class<Emp> empClass = Emp.class;
try {
//2、根据方法名字获取该类中的方法
Method method = empClass.getDeclaredMethod("hehe");
//3.获取该class对象的构造方法
Constructor<Emp> constructor = empClass.getConstructor();
//4.使用该类的构造方法将该类实例化
Emp emp = constructor.newInstance();
//5.设置方法可以访问权限
method.setAccessible(true);
//6.执行该类中的方法
method.invoke(emp);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
5.7 执行非静态有参方法
//执行非静态有参方法
//1.获取该类的class对象
Class<Emp> empClass = Emp.class;
try {
//2.根据方法名字获取该类中的方法 并传入参数类型.class
Method method = empClass.getDeclaredMethod("sum", Integer.class, Integer.class);
//3.获取该class对象的构造方法
Constructor<Emp> constructor = empClass.getConstructor();
//4.使用该类的构造方法将该类实例化
Emp emp = constructor.newInstance();
//5.设置方法可以访问权限
method.setAccessible(true);
//6.传入参数 执行该类中的方法
Integer num = (Integer) method.invoke(emp, 10, 20);
System.out.println(num);
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
}
5.8 操作属性 Field 以及遍历获取所有属性
//操作属性
//1.获取该类的class对象
Class<Emp> empClass = Emp.class;
try {
//2. 获取该class对象的构造方法
Constructor<Emp> constructor = empClass.getConstructor();
//3.使用该类的构造方法将该类实例化
Emp emp = constructor.newInstance();
//4.获取私有成员变量
Field namefield = empClass.getDeclaredField("name");
Field agefield = empClass.getDeclaredField("age");
Field genderfield = empClass.getDeclaredField("gender");
//5.设置属性可以访问权限
namefield.setAccessible(true);
agefield.setAccessible(true);
genderfield.setAccessible(true);
//6.给属性赋值
namefield.set(emp,"张益鸣");
agefield.set(emp,23);
genderfield.set(emp,1);
//7.读取属性值
String name = (String) namefield.get(emp);
Integer age = (Integer) agefield.get(emp);
Integer gender = (Integer) genderfield.get(emp);
System.out.println(name);
System.out.println(age);
System.out.println(gender);
//获取所有属性
Field[] declaredFields = empClass.getDeclaredFields();
for (Field field : declaredFields) {
field.setAccessible(true);
System.out.println(field.get(emp));
}
} catch (NoSuchMethodException | InstantiationException | IllegalAccessException | InvocationTargetException | NoSuchFieldException e) {
e.printStackTrace();
}