类的加载过程
三个阶段:加载-链接-初始化, 类的初始化只会执行1次。
加载
把字节码文件以流的形式加载到jvm中
链接
- 验证:验证字节码的合法性等以保证jvm的安全
- 准备:为静态变量赋予初始值,为静态常量赋予有效值。比如static int a = 1; static final int b = 2;
- 解析:将符号引用转换实际的地址引用
初始化
- 静态变量的直接显式赋值语句
- 静态代码块中的语句
注:代码里面声明某个类时,并不会马上初始化。
导致类初始化的几种情况
- 运行主方法所在的类,要先完成类初始化。
- 第一个使用某个类型,
new
这个类的实例对象。 - 调用某个类的静态成员或静态方法。
- 子类初始化时,父类还未初始化。
- 通过反射初始化某个类时,
Class.forName("类的全名");
不会导致类初始化的操作
- 调用类的静态常量,比如static final int a = 3。
- 通过子类调用父类的静态变量,只有父类才会初始化。
- 使用某个类型创建数组对象时。比如
B[] arr = new B[3];
- 使用某个类声明变量时不会初始化。
类加载器
类加载器分类
类的加载过程是通过类加载器完成的。
- 引导类加载器(BootsrapClassLoader)
- 主要加载jre/lib/rt目录下的jar包
- 此类加载器是c/c++编写的
- 扩展类加载器(ExtensionClassLoader)
- 主要加载jre/lib/ext目录下的文件
- 此类加载器ClassLoader的子类
- 应用程序类加载器(ApplicationClassLoader)
- 主要负责加载自己编写的代码编译后的字节码文件
- 在classpath路径下的类
- 自定义类加载器(SelfDefnitionClassLoader)
- 加载指定路径下的类
获取类加载器
public class Demo{
public static void main(String[] args){
//1.通过类的Class实例获取
ClassLoader classLoader = Demo.class.getClassLoader();
System.out.println(classLoader);
//2. 通过线程获取类加载器
ClassLoader classLoader1 = Thread.currentThread().getContextClassLoader();
System.out.println(classLoader1);
//3. 通过ClassLoader的静态方法
ClassLoader classLoader2 = ClassLoader.getSystemClassLoader();
//4. 获取父级类加载器
ClassLoader parent = classLoader.getParent();
System.out.println("parent = " + parent);
}
}
双亲委托机制
加载类时,先让自己的父类Extension ClassLoader去查看是否已经加载过,父类Extension ClassLoader接到委托后查询到未加载,先继续咨询父类Bootsrap ClassLoader来核实是否已经加载过。
作用:
- 避免重复加载已经加载过的类
- 防止有人通过类加载器注入恶意代码修改核心类
反射
Class实例
java创建了一个字节码类,表示所有的字节码文件。类加载器将字节码文件加载到jvm内存中时,相当于创建了字节码类的实例对象。该实例对象表示正在运行的java程序中的类和接口,该Class对象是唯一的。
获取Class实例的方法:
- 类名.class
- Class.forName方法(后期框架最常用的方法)
- Object类的getClass方法
- ClassLoader的类加载器对象可以用系统类加载对象加载指定路径下的类型
//1. 类名.class
String.class;
//2.Class.forName
Class.forName("java.lang.String");
//3.Object类的getClass方法
new Student().getClass();
//4.通过类加载器的静态方法
ClassLoader.getSystemClassLoader().loadClass("java.lang.String");
反射的概念
不是通过类的对象实例来直接操作这个类的属性和方法,而是通过类的Class对象实例来获取这个类的属性和方法。这个就是反射,通过反射的方式来操作一个类。
获取类的构造器:
public class Demo{
public static void main(String[] args){
//
Class<?> clazz = Class.forName("java.lang.String");
//获取所有public构造器
clazz.getConstructors();
//获取所有构造器,包括私有的
clazz.getDeclaredConstructors();
//获取指定的构造器
Constructor<?> c = clazz.getConstructor();//根据构造器参数获取对应的构造器
String s = c.newInstance();
//获取有参构造器
Constructor<?> c1= clazz.getConstructor(String.class);
//获取私有方法需要先设置访问权限
c1.setAccessible(true);
//调用getDeclaredXX方法
c1.getDeclaredContructor();
}
}
获取类的属性:
public class Demo{
public static void main(String[] args){
//
Class<?> clazz = Class.forName("java.lang.String");
//2. 获取所有声明的属性
Field[] declaredFields = clazz.getDeclaredFields();
//3.获取单个公共属性
Object name= clazz.getField("name");
Object obj = clazz.newInstance();//创建Student对象
//获取obj的name属性值
Object value = name.get
}
}
创建任意引用类型的对象
- 直接通过Class对象来实例化(要求必须有无参构造)
- 通过获取构造器对象来进行实例化
- 方式1:
- 获取该类型的Class对象
Class.forName()
- 创建对象
clazz.newInstance()
- 获取该类型的Class对象
- 方式2:
- 获取该类型的Class对象
Class.forName()
- 获取构造器对象
clazz.getDeclaredConstructor()
- 创建实例对象
contructor.newInstance()
- 获取该类型的Class对象
操作任意类型的属性
- 获取该类型的Class对象
- 获取属性对象Field
field = clazz.getDeclaredField("username");
- 访问私有属性时,需要设置属性可访问
setAccessilbe(true)
- 创建实例对象:如果操作的是非静态属性,需要创建实例对象Object obj = clazz.newInstance();
- 设置属性值
field.set(obj, "chai");
- 获取属性值
Object value = field.get(obj);
调用任意类型的方法
- 获取该类型的Class对象clazz
- 获取方法对象
method = getDeclaredMethod(方法名, 参数列表)
- 创建实例对象
clazz.newInstance()
- 调用方法
method.invoke(obj, 参数)
注解
注解也是一种注释,它不会改变程序原有的逻辑,只是对程序增加了某些注释性信息。不过它不同于单行注释和多行注释,对于单行注释和 多行注释是给程序员看的,而注解是可以被编译器或其他程序读取的一种注释,程序还可以根据注解的不同,做出相应的处理。注解是插入到代码中便于现有工具对它们进行处理的标签。
注解的作用
- 编写文档,注解可以生成文档
- 代码分析, 比如注解的反射
- 编译检查,比如@Override
常见注解:
注解名称 | 作用 |
---|---|
@author | 标识作者名 |
@version | 标识版本号 |
@Override | 表示该方法是重写方法 |
@Deprecated | 表示过时的 |
@SupressWarning | 抑制警告 |
自定义注解
public @interface MyAnno{
String value() default "java";//注解的属性
int num() default 1;
int[] nums();
}
- 定义了默认值时,注解使用时就可以不给对应的属性值赋值了。
- 如果只是给value属性赋值,可以省略value = ,直接写成
@MyAnno("hello")
元注解
- @Target(ElementType.TYPE):表示注解可以使用的位置
- TYPE:类
- FIELD:属性
- METHOD:方法
- @Retentioin():表示注解的有效周期
- SOURCE:在源码中有效
- CLASS:在字节码中有效
- RUNTIME:运行期间有效
- @Documented:表示注解信息可以提取到文档当中
- @Inherited:表示注解信息可以被继承
注解的解析
通过Java技术获取注解数据的过程则称为注解解析。通常通过反射获取注解及其属性值。
public class TestAnnotation {
public static void main(String[] args) throws ClassNotFoundException {
Class<?> clazz = Class.forName("com.d8_18._03anatation.exer.BookStore");
Book book = clazz.getAnnotation(Book.class);
String value = book.value();
double price = book.price();
String[] authors = book.authors();
System.out.println("authors = " + authors);
System.out.println("price = " + price);
System.out.println("value = " + value);
}
}