类加载
类在内存中的生命周期:加载–>使用–>卸载
类的加载又分为三个阶段:
(1)加载:load
指将类型的clas字节码数据读入内存。
- 通过类的全名,获取类的二进制数据流。
- 解析类的二进制数据流为方法区内的数据结构(Java类模型)
(2)连接:link
①验证:校验合法性等
-
格式验证:是否以魔数0XCAFEBABE开头,数据中每一个项是否都拥有正确的长度等。
-
语义检查:Java虚拟机会进行字节码的语义检查,但凡在语义上不符合规范的,虚拟机也不会给予验证通过。比如:
是否所有的类都有父类的存在(在Java里,除了Object外,其他类都应该有父类)
是否一些被定义为final的方法或者类被重写或继承了
非抽象类是否实现了所有抽象方法或者接口方法
②准备:准备对应的内存
- 准备对应的内存(方法区),创建Class对象,为类变量赋默认值,为静态常量赋初始值。
③解析:将类、接口、字段和方法的符号引用转为直接引用
- 符号引用就是一些字面量的引用,和虚拟机的内部数据结构和和内存布局无关。比较容易理解的就是在Class类文件中,通过常量池进行了大量的符号引用。但是在程序实际运行时,只有符号引用是不够的,系统需要明确知道数据的位置。
(3)初始化:initialize
1、哪些操作会导致类的初始化?
(1)运行主方法所在的类,要先完成类初始化,再执行main方法
(2)第一次使用某个类型就是在new它的对象,此时这个类没有初始化的话,先完成类初始化再做实例初始化
(3)调用某个类的静态成员(类变量和类方法),此时这个类没有初始化的话,先完成类初始化
(4)子类初始化时,发现它的父类还没有初始化的话,那么先初始化父类
(5)通过反射操作某个类时,如果这个类没有初始化,也会导致该类先初始化
类初始化执行的是(),该方法由(1)类变量的显式赋值代码(2)静态代码块中的代码构成
详解类的初始化和实例初始化的过程
2、哪些使用类的操作,但是不会导致类的初始化?
(1)使用某个类的静态的常量(static final)
(2)通过子类调用父类的静态变量,静态方法,只会导致父类初始化,不会导致子类初始化,即只有声明静态成员的类才会初始化
(3)用某个类型声明数组并创建数组对象时,不会导致这个类初始化
3、类加载器
1、类加载器分为:
(1)引导类加载器(Bootstrap Classloader)又称为根类加载器
它负责加载jre/rt.jar核心库
它本身不是Java代码实现的,也不是ClassLoader的子类,获取它的对象时往往返回null
(2)扩展类加载器(Extension ClassLoader)
它负责加载jre/lib/ext扩展库
它是ClassLoader的子类
(3)应用程序类加载器(Application Classloader)
它负责加载项目的classpath路径下的类
它是ClassLoader的子类
(4)自定义类加载器
当你的程序需要加载“特定”目录下的类,可以自定义类加载器;
当你的程序的字节码文件需要加密时,那么往往会提供一个自定义类加载器对其进行解码
后面会见到的自定义类加载器:tomcat中
2、Java系统类加载器的双亲委托模式(parents delegate)
简单描述:
下一级的类加载器,如果接到任务时,会先搜索是否加载过,如果没有,会先把任务往上传,如果都没有加载过,一直到根加载器,如果根加载器在它负责的路径下没有找到,会往回传,如果一路回传到最后一级都没有找到,那么会报ClassNotFoundException或NoClassDefError,如果在某一级找到了,就直接返回Class对象。
优势
1.采用双亲委派模式的是好处是Java类随着它的类加载器一起具备了一种带有优先级的层次关系,通过这种层级关系可以避免类的重复加载,当父亲已经加载了该类时,就没有必要子ClassLoader再加载一次。
2.安全,java核心api中定义类型不会被随意替换,假设通过网络传递一个名为java.lang.Integer的类,通过双亲委托模式传递到启动类加载器,而启动类加载器在核心Java API发现这个名字的类,发现该类已被加载,并不会重新加载网络传递的过来的java.lang.Integer,而直接返回已加载过的Integer.class,这样便可以防止核心API库被随意篡改。
应用程序类加载器 把 扩展类加载器视为父加载器,
扩展类加载器 把 引导类加载器视为父加载器。
4、类的卸载
java.lang.Class类
Java反射机制是 : 在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为Java语言的反射机制。
Class对象是反射的根源。
1、哪些类型可以获取Class对象
所有Java类型
//(1)基本数据类型和void
例如:int.class
void.class
//(2)类和接口
例如:String.class
Comparable.class
//(3)枚举
例如:ElementType.class
//(4)注解
例如:Override.class
//(5)数组
例如:int[].class
2、获取Class对象的四种方式
(1)类型名.class
要求编译期间已知类型
(2)对象.getClass()
获取对象的运行时类型
(3)Class.forName(类型全名称)
可以获取编译期间未知的类型
(4)ClassLoader的类加载器对象.loadClass(类型全名称)
可以用系统类加载对象或自定义加载器对象加载指定路径下的类型
反射的应用
获取类型的详细信息
//1、先得到某个类型的Class对象
Class clazz = String.class;
//比喻clazz好比是镜子中的影子
//2、获取类信息
//(1)获取包对象,即所有java的包,都是Package的对象
Package pkg = clazz.getPackage();
System.out.println("包名:" + pkg.getName());
//(2)获取修饰符
//其实修饰符是Modifier,里面有很多常量值
/*
* 0x是十六进制
* PUBLIC = 0x00000001; 1 1
* PRIVATE = 0x00000002; 2 10
* PROTECTED = 0x00000004; 4 100
* STATIC = 0x00000008; 8 1000
* FINAL = 0x00000010; 16 10000
* ...
*
* 设计的理念,就是用二进制的某一位是1,来代表一种修饰符,整个二进制中只有一位是1,其余都是0
*
* mod = 17 0x00000011
* if ((mod & PUBLIC) != 0) 说明修饰符中有public
* if ((mod & FINAL) != 0) 说明修饰符中有final
*/
int mod = clazz.getModifiers();
System.out.println(Modifier.toString(mod));
//(3)类型名
String name = clazz.getName();
System.out.println(name);
//(4)父类,父类也有父类对应的Class对象
Class superclass = clazz.getSuperclass();
System.out.println(superclass);
//(5)父接口们
Class[] interfaces = clazz.getInterfaces();
for (Class class1 : interfaces) {
System.out.println(class1);
}
//获取构造器、方法和属性
Class<Student> clazz = Student.class;
//构造器不参与继承,只能获取本类的构造器。
for (Constructor<?> constructor : clazz.getConstructors()) {//获取本类所有公共的构造器
System.out.println(constructor);
}
Constructor<Student> constructor = clazz.getConstructor(String.class, Integer.class, Double.class);//获取指定构造器
System.out.println("constructor = " + constructor);
Constructor<?>[] declaredClasses = clazz.getDeclaredConstructors();//获取本类所有的构造器包含私有的
for (Constructor<?> declaredClass : declaredClasses) {
System.out.println("declaredClass = " + declaredClass);
}
Constructor<Student> declaredConstructor = clazz.getDeclaredConstructor(String.class, Double.class);
System.out.println("declaredConstructor = " + declaredConstructor);//获取指定构造器包含私有的
//使用私有构造器时要先暴力反射
declaredConstructor.setAccessible(true);
Student student = declaredConstructor.newInstance("张三", 99.6);
System.out.println("student = " + student);
for (Field field : clazz.getFields()) { //获取所有公共的成员变量包含继承的公共成员变量
System.out.println("field = " + field);
}
Field score = clazz.getField("score");//获取指定公共的成员变量包含继承的公共成员变量
System.out.println("score = " + score);
//getDeclaredField、getDeclaredFields都只能获取本类的成员变量。
Field name = clazz.getDeclaredField("name");//获取指定的成员变量包含私有的
//使用私有的成员变量时要先暴力反射
name.setAccessible(true);
name.set(student, "小明");//给指定对象成员变量赋值
Object o = name.get(student);//返回name属性值,没有时则返回null
System.out.println("o = " + o);
System.out.println("student = " + student);
System.out.println("name = " + name);
for (Field declaredField : clazz.getDeclaredFields()) {//获取所有成员变量
System.out.println("declaredField = " + declaredField);
}
for (Method method : clazz.getMethods()) {//获取本类及父类所有的公共方法(包含间接继承的父类(object)公共方法)
System.out.println("method = " + method);
}
Method setName = clazz.getMethod("setName", String.class);//获取本类及父类指定的公共方法(包含间接继承的父类(object)公共方法)
Object stu = setName.invoke(student, "李四");//看此方法有无返回值类型,如果没有就返回null,反射在此处相当于为所有方法返回值做准备
System.out.println("stu = " + stu);//null
for (Method declaredMethod : clazz.getDeclaredMethods()) {//获取本类所有的方法(包含私有方法)
System.out.println("declaredMethod = " + declaredMethod);
}
Method say = clazz.getDeclaredMethod("say", String.class, String.class);//获取本类指定的私有方法
//使用私有的方法时先暴力反射
say.setAccessible(true);
say.invoke(student,"张三","hello");//相当于对象调方法传参数
创建任意引用类型的对象
两种方式:
1、直接通过Class对象来实例化(要求必须有无参构造)
2、通过获取构造器对象来进行实例化
方式一的步骤:
(1)获取该类型的Class对象(2)创建对象
方式二的步骤:
(1)获取该类型的Class对象(2)获取构造器对象(3)创建对象
如果构造器的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
操作任意类型的属性
(1)获取该类型的Class对象
Class clazz = Class.forName(“com.atguigu.bean.User”);
(2)获取属性对象
Field field = clazz.getDeclaredField(“username”);
(3)设置属性可访问
field.setAccessible(true);
(4)创建实例对象:如果操作的是非静态属性,需要创建实例对象
Object obj = clazz.newInstance();
(4)设置属性值
field.set(obj,“chai”);
(5)获取属性值
Object value = field.get(obj);
如果操作静态变量,那么实例对象可以省略,用null表示
调用任意类型的方法
(1)获取该类型的Class对象
Class clazz = Class.forName(“com.atguigu.service.UserService”);
(2)获取方法对象
Method method = clazz.getDeclaredMethod(“login”,String.class,String.class);
(3)创建实例对象
Object obj = clazz.newInstance();
(4)调用方法
Object result = method.invoke(obj,“chai”,"123);
如果方法的权限修饰符修饰的范围不可见,也可以调用setAccessible(true)
如果方法是静态方法,实例对象也可以省略,用null代替