当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。骤统称为类加载或类初始化。
类加载和反射 - 图1

类加载器

类加载器概述

类加载就是将磁盘上的class文件加载到内存中。虚拟机设计团队把类加载阶段的”通过一个类的全限定名获取描述此类的二进制字节流”这个动作放到Java虚拟机外部去实现,以便让应用程序自己决定如何去获取所需要的类。实现这个动作的代码模块称为”类加载器”。类加载器是JVM执行类加载机制的前提。

类加载命名空间

类的唯一性

对于任意一个类,都需要由加载它的类加载器和这个类本身一同确认其在Java虚拟机中的唯一性。
每一个类加载器,都会拥有一个独立的类名称空间:
比较两个类是否相等,只有在这两个类是由同一类加载器加载的前提下才有意义。否则即使这两个类源自同一Class文件,只要加载他们类加载器不同,那这两个类就必定不相等。

命名空间

每一个类加载器都有自己命名空间,命名空间由该加载器及所有的父加载器锁加载的类组成。
在同一命名空间中,不会出现类的完整名字(包括类的包名)相同的两个类,只加载一次类。
在不同的命名空间中,有可能会出现类的完整名字(包括类的包名)相同的两个类,可以加载多次。

类加载机制

双亲委派机制

可见性
单一性

类加载(ClassLoader)就是专门用来加载类的工具。
所有的Class文件都是由ClassLoader进行加载到JVM中,转换成目标类对应的java.lang.Class对象实例。
JVM将所有Class对象进行链接、初始化等操作。
因此,ClassLoader在整个装载阶段,只能影响到类的加载,而无法通过ClassLoader去改变类的链接和初始化行为。至于他是否可以运行,则由Execution Engine决定。
jdk中自带3个类加载器

  • 启动类加载器
  • 扩展类加载器
  • 应用类加载器

代码在运行之前,会将所有涉及的类加载到JVM中。

通过java反射机制可以直接操作字节码文件(class文件)。
java.lang.Class 代表字节码文件,代表一个类型(所有类的类,所有类都是类Class的实例)
java.lang.reflect.Method 字节码中的方法
java.lang.reflect.Constructor 字节码中的构造器
java.lang.reflect.Field 字节码中的属性(包括静态变量、实例变量)

获取Class对象

forName()方法

  1. package priv.yqs;
  2. public class ReflectDemo01 {
  3. public static void main(String[] args) throws ClassNotFoundException {
  4. // forName方法是Class提供的静态方法
  5. // forName方法的参数是字符串
  6. // 字符串内容是需要反射的类的全限定类名
  7. Class<?> clazz = Class.forName("java.lang.String");
  8. // clazz就代表String类的字节码文件
  9. System.out.println("java.lang.String的字节码文件: " + clazz);
  10. }
  11. }

getClass()方法

  1. package priv.yqs;
  2. public class ReflectDemo02 {
  3. public static void main(String[] args) {
  4. // Class中定义了getClass方法,Class是所有类的父类,故每个类都有getClass方法
  5. // 每个类都可以由实例调用getClass方法得到该类的字节码文件(该类的类型)
  6. String str = "enqingshan";
  7. Class<? extends String> aClass = str.getClass();
  8. // clazz就代表String类的字节码文件
  9. System.out.println("java.lang.String类型: " + aClass);
  10. }
  11. }

class属性

  1. package priv.yqs;
  2. public class ReflectDemo03 {
  3. public static void main(String[] args) {
  4. // 任何类型包括基本数据类型都有class属性,任何类型都可以通过class属性得到该类型的类型
  5. Class<Integer> aClass = int.class;
  6. // clazz就代表Integer类的字节码文件
  7. System.out.println("java.lang.Integer类型: " + aClass);
  8. }
  9. }

通过反射得到实例

  1. package priv.yqs;
  2. public class ReflectDemo04 {
  3. private String name;
  4. private int age;
  5. private String gander;
  6. public ReflectDemo04() {
  7. System.out.println("priv.yqs.ReflectDemo04无参构造器被调用");
  8. }
  9. public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
  10. // 获取ReflectDemo04类型的类型
  11. Class<?> aClass = Class.forName("priv.yqs.ReflectDemo04");
  12. // newInstance()方法底层会调用字节码对象中的无参构造器来实例化对象
  13. // 通过字节码对象反射获得ReflectDemo04实例
  14. ReflectDemo04 reflectDemo04 = (ReflectDemo04) aClass.newInstance();
  15. }
  16. }

场景一

在不更改源代码条件下,近修改配置文件便可以得到不同类的实例

  1. className=priv.yqs.ReflectDemo05
  1. package priv.yqs;
  2. import java.util.ResourceBundle;
  3. public class ReflectDemo05 {
  4. public ReflectDemo05() {
  5. System.out.println("priv.yqs.ReflectDemo05无参构造器被调用");
  6. }
  7. public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
  8. // 获取配置文件内容
  9. ResourceBundle res = ResourceBundle.getBundle("className");
  10. // 获取ReflectDemo04类型的类型
  11. Class<?> aClass = Class.forName(res.getString("className"));
  12. // newInstance()方法底层会调用字节码对象中的无参构造器来实例化对象
  13. // 通过字节码对象反射获得ReflectDemo04实例
  14. ReflectDemo05 reflectDemo05 = (ReflectDemo05) aClass.newInstance();
  15. }
  16. }

场景二

只想使用某些类中定义的静态代码块,可以使用forName()方法加载类,此时该类只被加载,而不会被实例化

  1. package priv.yqs;
  2. import java.util.ResourceBundle;
  3. public class ReflectDemo06 {
  4. static {
  5. System.out.println("priv.yqs.ReflectDemo05中静态代码块被执行");
  6. }
  7. public ReflectDemo06() {
  8. System.out.println("priv.yqs.ReflectDemo05无参构造器被调用");
  9. }
  10. public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {
  11. // 获取配置文件内容
  12. ResourceBundle res = ResourceBundle.getBundle("className");
  13. // 获取ReflectDemo04类型的类型
  14. Class<?> aClass = Class.forName(res.getString("className"));
  15. }
  16. }
  17. // 只会打印:priv.yqs.ReflectDemo05中静态代码块被执行

获取类路径

  1. public class ReflectDemo07 {
  2. public static void main(String[] args) throws IOException {
  3. // 获取类路径下文件绝对路径
  4. String path = Objects.requireNonNull(
  5. Thread.currentThread().
  6. getContextClassLoader().
  7. getResource("className.properties")).
  8. getPath();
  9. System.out.println("path = " + path);
  10. InputStream is = Thread.currentThread().
  11. getContextClassLoader().
  12. getResourceAsStream("className.properties");
  13. Properties prop = new Properties();
  14. prop.load(is);
  15. Object className = prop.get("className");
  16. System.out.println("className = " + className);
  17. }
  18. }

ResourceBundle

  1. public class ReflectDemo07 {
  2. public static void main(String[] args) throws IOException {
  3. // 文件在类路径下
  4. // 扩展名必须是properties
  5. // getBundle("className")不加扩展名
  6. ResourceBundle rsb = ResourceBundle.getBundle("className");
  7. String rsbString = rsb.getString("className");
  8. System.out.println("rsbString = " + rsbString);
  9. }
  10. }