类的加载过程和反射以及注解

类的加载过程

三个阶段:加载-链接-初始化, 类的初始化只会执行1次。

加载

把字节码文件以流的形式加载到jvm中

链接

  1. 验证:验证字节码的合法性等以保证jvm的安全
  2. 准备:为静态变量赋予初始值,为静态常量赋予有效值。比如static int a = 1; static final int b = 2;
  3. 解析:将符号引用转换实际的地址引用

初始化

  1. 静态变量的直接显式赋值语句
  2. 静态代码块中的语句

类的加载过程和反射以及注解_第1张图片

注:代码里面声明某个类时,并不会马上初始化。

导致类初始化的几种情况

  1. 运行主方法所在的类,要先完成类初始化。
  2. 第一个使用某个类型,new这个类的实例对象。
  3. 调用某个类的静态成员或静态方法。
  4. 子类初始化时,父类还未初始化。
  5. 通过反射初始化某个类时,Class.forName("类的全名");

不会导致类初始化的操作

  1. 调用类的静态常量,比如static final int a = 3。
  2. 通过子类调用父类的静态变量,只有父类才会初始化。
  3. 使用某个类型创建数组对象时。比如B[] arr = new B[3];
  4. 使用某个类声明变量时不会初始化。

类加载器

类加载器分类

类的加载过程是通过类加载器完成的。

  • 引导类加载器(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来核实是否已经加载过。

类的加载过程和反射以及注解_第2张图片
作用:

  1. 避免重复加载已经加载过的类
  2. 防止有人通过类加载器注入恶意代码修改核心类

反射

Class实例

java创建了一个字节码类,表示所有的字节码文件。类加载器将字节码文件加载到jvm内存中时,相当于创建了字节码类的实例对象。该实例对象表示正在运行的java程序中的类和接口,该Class对象是唯一的。

获取Class实例的方法

  1. 类名.class
  2. Class.forName方法(后期框架最常用的方法)
  3. Object类的getClass方法
  4. 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
	}
}

创建任意引用类型的对象

  1. 直接通过Class对象来实例化(要求必须有无参构造)
  2. 通过获取构造器对象来进行实例化
  • 方式1:
    • 获取该类型的Class对象Class.forName()
    • 创建对象clazz.newInstance()
  • 方式2:
    • 获取该类型的Class对象Class.forName()
    • 获取构造器对象clazz.getDeclaredConstructor()
    • 创建实例对象contructor.newInstance()

操作任意类型的属性

  • 获取该类型的Class对象
  • 获取属性对象Field field = clazz.getDeclaredField("username");
  • 访问私有属性时,需要设置属性可访问setAccessilbe(true)
  • 创建实例对象:如果操作的是非静态属性,需要创建实例对象Object obj = clazz.newInstance();
  • 设置属性值 field.set(obj, "chai");
  • 获取属性值Object value = field.get(obj);

调用任意类型的方法

  1. 获取该类型的Class对象clazz
  2. 获取方法对象method = getDeclaredMethod(方法名, 参数列表)
  3. 创建实例对象clazz.newInstance()
  4. 调用方法 method.invoke(obj, 参数)

注解

注解也是一种注释,它不会改变程序原有的逻辑,只是对程序增加了某些注释性信息。不过它不同于单行注释和多行注释,对于单行注释和 多行注释是给程序员看的,而注解是可以被编译器或其他程序读取的一种注释,程序还可以根据注解的不同,做出相应的处理。注解是插入到代码中便于现有工具对它们进行处理的标签。

注解的作用

  1. 编写文档,注解可以生成文档
  2. 代码分析, 比如注解的反射
  3. 编译检查,比如@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);
    }
}

你可能感兴趣的:(java)