Day24
1 虚拟机类加载机制
- 概述:
如果需要使用某一个类型,虚拟机把描述类的数据从class文件中加载到运行内存,并 对数据进行校验,转换解析和初始化,最终形成可以被java虚拟机直接使用的类型, 这就是虚拟机的类加载机制。
- 加载机制的过程:
当程序要使用某个类时,如果该类还未被加载到内存中,系统会通过加载,连接,初始 化三步来实现对这个类的加载。
(1)加载:就是指将class文件中的信息读入内存,并为之创建一个Class对象
注意:任何类被使用时系统都会建立一个Class对象
(2)连接:
验证是字节码对象是否有正确的内部结构,检验是否符合官方制定的class文件规范
(3)初始化:负责为类的静态成员分配内存,并设置默认初始化值 - 类加载时机:
(1)创建类的实例 new Person();
(2)类的静态成员使用 Person.name;
(3)使用反射方式来访问类型时
(4)初始化某个类的子类 (在加载子类时,也需要先对父类类型加载)
(5)直接使用java.exe命令来运行某个主类
2 类加载器
-
概述:类加载器是负责加载类的对象。
将class文件中的数据加载到运行内存中,并为之生成对应的字节码对象。 -
分类:
(1)Bootstrap ClassLoader
引导类加载器
也被称为根类加载器,负责Java核心类的加载。
(2)Extension ClassLoader
扩展类加载器
(3)Application ClassLoader
系统类加载器
负责在JVM启动时加载来自java命令的class文件 -
类加载器之间的继承关系
-Bootstrap ClassLoader
-Extension ClassLoader
-Application ClassLoader -
类加载器加载类型的双亲委派机制:
(1)概述:双亲委派机制是指当一个类加载器收到一个类加载请求时,该类加载器首先会把请求委派给父类加载器,每个类加载器都是如此,只有在父类加载器在自己的搜索范围内找不到指定类时,子类加载器才会尝试自己去加载
(2)举例:定义了一个类型Person想要加载该类型,Application 收到加载类的请求,再收到 该请求时,并不是直接加载而是将该请求委派他的父类加载器(Extension ),当 Extension 加载器收到子类委派的请求时,Extension 加载并不是直接加载该类型, 而是委派给他的父类Bootstrap ,当Bootstrap 收到请求时,Bootstrap 加载器如果 不能加载该类型,再将请求返回给子类加载器Extension ,Extension 也不能加载 该请求,再将加载的任务返回给他的子类Application ,Application 可以加载该类 型,就直接加载,如果Application 也不能加载,表示根本没有定义过Person,就表 示加载失败。
使用了一个类型String,Application 收到加载类的请求,并不是直接加载,将请求 给其父类Extension ,Extension 将请求给其父类Bootstrap ,Bootstrap 收到请求 之后,Bootstrap 加载器可以加载String类型,就直接加载。
3 反射
- 概述:
反射是指在加载类型之后,通过该类的字节码对象来获取需要的信息,然后通过获取到 的信息来实现对应的功能,这种机制就是反射。由于这种动态性,可以极大的增强程序 的灵活性和扩展性。 - 获取类字节码对象的方式:(使用反射的前提)
(1)类名.class
(2)对象名的.getClass()
(3)Class.forName(类的全类名)
张三 23 李四 24 :Person.class(字节码对象)
小猫 黄色 小狗 黑 色:Animal.class(字节码对象) Class类型
代码
package demos1;
import java.util.Scanner;
public class Demo01 {
public static void main(String[] args) throws ClassNotFoundException {
//获取Person类的字节码对象
Class c1 = Person.class;
Class c2 = new Person().getClass();
//该方式,加载类型使用的是字符串
//因为字符串的来源很广泛,所以可以从各地获取字符串
//以后获取的字符串是哪一个类型,就可以加载哪一个类型(提供扩展性)
//虽然传递的是一个字符串,但是虚拟机会去本地中寻找有么有该字符串对应的字节码文件,如果有对应的文件,就自动加载
Class c3 = Class.forName("demos1.Person");
//因为一个类型只加载一次,所以获取类的字节码对象只有一个
System.out.println(c1 == c2);//true
System.out.println(c1 == c3);//true
System.out.println(c3 == c2);//true
}
}
3.1 反射获取字节码对象中的构造方法并实例化
- 概述:根据类的字节码对象获取该类的构造方法,并创造该类对象
- 获取构造方法的方式:
函数 | Value |
---|---|
getConstructors() | 返回所有公共的构造方法对象 |
getDeclaredConstructors() | 返回所有构造方法对象 |
getConstructor() | 返回空参构造对象 |
getConstructor(Class<?>... parameterTypes) | 返回单个指定参数的公共有参构造方法对象 |
getDeclaredConstructor(Class<?>...parameterTypes) | 返回单个指定参数的有参构造方法对象 |
- 通过构造方法对象,创建类的实例:
newInstance(Object...initargs)
如果是空参构造,就不需要给出实参
如果是有参构造对象,就需要给出对应的实际参数
代码
package demos1;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public class Demo02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取类的字节码对象
Class<?> cla = Class.forName("demos1.Person");
//获取字节码对象中维护的构造方法
//获取公共的构造方法对象
// Constructor<?>[] cons = cla.getConstructors();
//获取所有的构造方法对象
Constructor<?>[] cons = cla.getDeclaredConstructors();
for(Constructor con:cons){
System.out.println(con);
}
System.out.println("..............................");
Constructor<?> con1 = cla.getConstructor();//获取空参构造方法对象
System.out.println(con1);
Constructor<?> con2 = cla.getConstructor(String.class, int.class);//获取有参构造方法对象
System.out.println(con2);
Constructor<?> con3 = cla.getDeclaredConstructor(String.class);//获取私有的有参构造方法对象
System.out.println(con3);
//构造方法对象已经获取,就要使用获取的构造方法创建对象
Object o = con1.newInstance();
System.out.println(o);
Object o2 = con2.newInstance("张三",23);
System.out.println(o2);
}
}
4 案例
package demos2_test;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.Scanner;
public class Test02 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//想要喝什么水果,输入对应的序号即可
Scanner sc = new Scanner(System.in);
while(true){
System.out.println("请录入您要买的果汁序号:");
System.out.println("1、苹果汁");
System.out.println("2、橘子汁");
System.out.println("3、西瓜汁");
System.out.println("4、关门");
String str = null;
int x = sc.nextInt();
switch(x){
case 1:
str = "demos2_test.Apple";
break;
case 2:
str = "demos2_test.Orange";
break;
case 3:
str = "demos2_test.WaterMelan";
break;
case 4:
return;
}
Class<?> cla = Class.forName(str);
Constructor<?> con = cla.getConstructor();
Object o = con.newInstance();
Fruit f = (Fruit)o;
JuiceMachine jm = new JuiceMachine();
jm.makeJuice(f);
}
}
}
5 反射获取成员变量并使用
- 获取方法:
函数 | Value |
---|---|
getFields() | 返回所有公共成员变量对象 |
getDeclaredFields() | 返回所有成员变量对象 |
getField(String name) | 返回指定的公共成员变量对象 |
getDeclaredField(String name) | 返回单个成员变量对象 |
- 访问成员属性的方法:
set(Object obj,Object value)
: 用于给obj对象中的该成员变量赋value值
get(Object obj)
:用于获取obj对象的指定成员变量值 - 暴力反射:
概述:如果类型中的某些属性是私有化的,那么就不能直接使用方法访问该属性
所以只能使用暴力反射来强制访问。
相关方法:
isAccessible()
:判断当前属性对象是否需要参与虚拟机的检测
setAccessible(boolean flag)
:将当前对象设置为不需要被虚拟机检测
代码
package demos1;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
public class Demo03 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchFieldException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//使用反射的方式获取类中的成员变量
Class<?> cla = Class.forName("demos1.Person");
//获取成员变量,获取的也是一个个对象来表示的属性
// Field[] fs = cla.getFields();//获取public修饰的属性对象
Field[] fs = cla.getDeclaredFields();//获取所有的属性对象
for(Field f:fs){
System.out.println(f);
}
System.out.println("........................");
//获取某一个属性对象(通过属性的名字来指定,名字定义为一个字符串)
Field hobby = cla.getField("hobby");
System.out.println(hobby);
Field name = cla.getDeclaredField("name");
System.out.println(name);
System.out.println(hobby.isAccessible());//返回值为false.表示使用当前属性时,需要经过虚拟机检测,如果是公共的就可以使用
System.out.println(name.isAccessible());//返回值为false,表示使用当前属性时,需要经过虚拟机检测,如果是私有的,不能使用
name.setAccessible(true);//将当前name属性设置为true之后,后续再使用该属性时,虚拟机不会检测,不管是私有还是公共的都可以直接使用
//使用空参创建一个对象
Object o = cla.newInstance();
//获取该对象中的属性值
System.out.println(hobby.get(o));
System.out.println(name.get(o));
//给对象o的属性赋值
hobby.set(o,"抽烟喝酒烫头");
System.out.println(hobby.get(o));
name.set(o,"于谦");
System.out.println(name.get(o));
}
}
6 反射获取成员方法并使用
- 方法:
函数 | Value |
---|---|
getMethods() | 返回所有公共成员方法对象 |
getDeclaredMethods() | 返回所有成员方法对象 |
getMethod(String methodName, Class<?>...parameterTypes) | 返回指定的公共成员方法对象 |
getDeclaredMethod(String methodName, Class<?>...parameterTypes) | 返回指定的成员方法对象 |
- 执行方法的方式:
invoke(Object o,Class...paramsTypre)
- 如果方法是已经非静态方法,需要传递一个对象执行
- 如果方法是一个静态方法,不需要传入对象,直接传入Null
代码
package demos1;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Demo04 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InstantiationException, InvocationTargetException {
//使用反射获取成员方法并使用
//先获取类型的字节码对象
Class cla = Class.forName("demos1.Person");
// Method[] ms = cla.getMethods();//获取类中公共的方法对象
Method[] ms = cla.getDeclaredMethods();//获取类中所有的(私有的)方法对象
for(Method m:ms){
System.out.println(m);
}
System.out.println("....................");
//使用方法名获取对应的方法对象(名使用字符串传递)
//如果方法是有参数的,再获取时,需要跟上参数的类型
Method show1 = cla.getMethod("show");
System.out.println(show1);
Method show2 = cla.getMethod("show",String.class);
System.out.println(show2);
Method print = cla.getDeclaredMethod("print");
System.out.println(print);
Method get = cla.getMethod("get", String.class);
System.out.println(get);
System.out.println("........................");
Object o = cla.newInstance();
//执行方法
show1.invoke(o);
show2.invoke(o,"show2 ");
int os = ((Integer)get.invoke(o, "123")).intValue();
System.out.println(os);
//通过暴力反射的方式,调用私有方法
print.setAccessible(true);
print.invoke(o);
//获取静态方法并使用,在使用时,不需要传入对象,参数给null即可
Method eat = cla.getMethod("eat");
eat.invoke(null);
}
}
7 案例
需求:
定义一个List集合(泛型定义为Integer),如:ArrayList list = new ArrayList<>();
要求利用反射的原理,在集合中添加若干字符串并且不报错,最终输出集合中的内容
package demos1;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
public class Test {
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
// 定义一个List集合(泛型定义为Integer),如:ArrayList<Integer> list = new ArrayList<>();
// 要求利用反射的原理,在集合中添加若干字符串并且不报错,最终输出集合中的内容
ArrayList<Integer> list = new ArrayList<>();
list.add(111);
list.add(222);
list.add(333);
Class<? extends ArrayList> cla = list.getClass();
//在使用一个带着泛型的类型时
//正常情况,该类型只能使用泛型定义的参数
//使用反射,可以越过泛型类型,执行其父类类型(泛型擦除)
Method add = cla.getMethod("add", Object.class);
add.invoke(list,"abc");
add.invoke(list,"xyz");
System.out.println(list);
}
}
8 反射练习
- 自定义一个Stu类型
属性:String name; int age (私有化,定义公共的访问方式)
方法:show(展示当前属性的信息) - 将这个类型的全类名定义在文件中
- 读取文件中的类名,并创建一个该类对象(通过有参构造创建)
- 使用show方法展示对象属性
- 使用公共的访问方法给属性改值,并再次调用show方法展示属性值
代码
package demos1;
import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test02 {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
BufferedReader br = new BufferedReader(new FileReader("day24/src/demos1/x.txt"));
String str = br.readLine();
Class<?> cla = Class.forName(str);
Constructor<?> con = cla.getConstructor(String.class, int.class);
Stu stu = (Stu)con.newInstance("张三", 23);
Method show = cla.getMethod("show");
show.invoke(stu);
Field name = cla.getDeclaredField("name");
name.setAccessible(true);
name.set(stu,"张三丰");
Field age = cla.getDeclaredField("age");
age.setAccessible(true);
age.set(stu,100);
show.invoke(stu);
}
}