类加载和反射

时间:2021-11-24 01:11:14

1. 类的加载和初始化

在程序用到一个类的时候,如果类还没有加载到内存,JVM会对类进行加载、连接和初始化。
类加载由类加载器完成,类加载器通常由JVM提供(系统类加载器)。开发者也可以通过继承ClassLoader创建自己的类加载器。
类加载器加载类的二进制数据,可以从以下几个地方获得:

  1. 从本地文件系统加载class文件
  2. 从JAR包加载class文件
  3. 网络加载class文件
  4. 动态编译Java源文件实现加载。
    类被加载后,系统会为类生成一个对应的Class对象,通过类连接把类的二进制数据合并到JRE中,称为类连接。类连接有三个阶段:
  5. 验证:检验被加载的类是否有正确的内部结构
  6. 准备:为类的静态属性分配内存,设置默认初始值
  7. 解析:将二进制数据中的符号引用替换成直接引用
    虚拟机对类进行初始化,主要是对静态属性进行初始化。声明静态属性的初始值,可在声明静态属性时指定初始值,也可以使用静态初始块为静态属性指定初始值。
public class ClassTest {
	static int a = 1;// 声明的时候执行初始值
	static int b;
	static {// 静态代码块,第一次加载类的时候执行
		System.out.println("静态代码块");
		System.out.println(b);// 输出默认值0
		b = 2;
		System.out.println(b);// 输出2
	}
	{//每次new对象都会执行
		System.out.println("实例代码块");
	}
}

测试代码:

public static void main(String[] args) {
	ClassTest test1 = new ClassTest();
	ClassTest test2 = new ClassTest();
}

输出结果:

JVM初始化一个类的时候,步骤如下:

  1. 如果类没被加载和连接,程序先加载并连接该类。
  2. 如果该类的直接父类没有被初始化,则先初始化直接父类
  3. 如果类中有初始化语句,则系统依次执行这些初始化语句
    JAVA中通过以下6中方法,可以触发一个类的初始化:
    a) 创建类的实例。new一个对象、通过反序列化或反射来创建类的实例
    b) 调用某个类的静态方法
    c) 访问某个类或接口的静态属性,或为静态属性赋值
    d) 使用反射方式强制创建某个类的java.lang.Class对象
    e) 初始化某个类的子类,该子类的所有父类都会被初始化
    f) 用java.exe运行某个主类
    另外,对于一个final修饰的静态属性,如果该属性在编译时就能得到属性值,访问该属性时不会触发类的初始化。如:
public class ClassTest {
	final static int a = 1;// 声明的时候执行初始值
}
public static void main(String[] args) {
	System.out.println(ClassTest.a);//不会触发ClassTest初始化
}

2. 类加载器

类加载器负责将.class文件加载到内存中,并生成对应的java.lang.Class对象。同一个类只能被加载到JVM中一次。在Java中,用类的全限定名(包名+类名)作为标识,区分是不是同一个类,即同一个包下不能有重名的类。JVM在加载类的时候,用类的全限定名+加载器的名字作为唯一标识。即同一个类可以被不同的类加载器加载,每个类加载器只能加载一次。
JVM启动时,有三个类加载器:

  1. Bootstrap ClassLoader:根类加载器,负责加载Java的核心类,它不是ClassLoader的子类,而是由JVM自身实现的。核心类在jre/lib下
  2. Extension ClassLoader:扩展类加载器,负责加载JRE扩展目录(jre/lib/ext)中的JAR包
  3. System ClassLoader:系统类加载器,负责在JVM启动时,加载来自java中的-classpath选项或java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。
    JVM加载类主要有如下三种机制:
  4. 全盘负责:当一个类加载器负责加载某个Class的时候,该Class所依赖的和引用的其它Class将由该类加载器负责载入。除非显示使用另外一个类加载器来载入。
  5. 父类委托:先让parent类加载器加载该Class,只有当parent加载器无法加载该类时才尝试从自己的类路径中加载该类。
  6. 缓存机制:缓存机制将会保证所有被加载过的Class都会被缓存,当程序中需要使用某个Class时,类加载器先从缓存中搜寻该Class,只有当缓存中不存在时,系统才会重新读取该类的二进制数据,并将其转换为Class对象。因此修改了Class后必须重新启动JVM。
public static void main(String[] args) throws IOException {
		ClassLoader systemLoader = ClassLoader.getSystemClassLoader();
		System.out.println(systemLoader);
		// 获取系统类加载器的加载路径,通常是CLASSPATH环境变量指定。
		// 如果没有指定CLASSPATH,默认是当前路径为系统类加载器的加载路径
		Enumeration<URL> eml = systemLoader.getResources("");
		while (eml.hasMoreElements()) {
			System.out.println(eml.nextElement());
		}
		ClassLoader parentLoader = systemLoader.getParent();
		System.out.println("扩展类加载器:"+parentLoader);
		System.out.println("扩展类加载器的加载路径:"+System.getProperty("java.ext.dirs"));
		System.out.println("扩展类加载器的parent:"+parentLoader.getParent());
	}

3. 反射

3.1 获得Class对象

每个类被加载后,都会为该类生成一个对应的Class对象,通过该Class对象可以访问JVM中的这个类。Java中获得Class对象有三种方式:

  1. 使用Class.forName(“类全限定名”)
  2. 调用某个类的class属性。如Person.class
  3. 调用某个对象的getClass()方法。
    通过Class对象可以访问对象的构造器、方法、属性。
    3.2 使用反射操作对象
    Class类中有可以访问类的Constructor(构造方法)、Methods(方法)、Field(属性)
    方法名中带Declared的,表示类中声明的成员,无关访问修饰符。
package classtest;

public class ClassTest {
	private String name;

	public ClassTest() {
		System.out.println("ClassTest无参构造");
	}

	public ClassTest(String name) {
		this.name = name;
		System.out.println("ClassTest有参构造");
	}

	public void info() {
		System.out.println("info()");
	}

	public void info(String in) {
		System.out.println("info(String in)" + in);
	}

	private void pri() {

	}
}

public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, SecurityException {
		Class clazz =Class.forName("classtest.ClassTest");
		Constructor[] constructors = clazz.getDeclaredConstructors();// 获取所有构造器
		for (Constructor cons : constructors) {
			System.out.println(cons);
		}
		Method[] methods = clazz.getMethods();// 获取类中所有public方法
		for (Method m : methods) {
			System.out.println(m);
		}
		Method info = clazz.getMethod("info", String.class);// 获取info(String)这个方法
		Field[] fields = clazz.getDeclaredFields();//获取所有声明的属性,与访问权限无关
		for(Field f:fields){
			System.out.println(f);
		}
	}