当程序主动使用某个类时,如果该类还未被加载到内存中,则JVM会通过加载、连接、初始化3个步骤来对该类进行初始化。骤统称为类加载或类初始化。
类加载器
类加载器概述
类加载就是将磁盘上的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()方法
package priv.yqs;public class ReflectDemo01 {public static void main(String[] args) throws ClassNotFoundException {// forName方法是Class提供的静态方法// forName方法的参数是字符串// 字符串内容是需要反射的类的全限定类名Class<?> clazz = Class.forName("java.lang.String");// clazz就代表String类的字节码文件System.out.println("java.lang.String的字节码文件: " + clazz);}}
getClass()方法
package priv.yqs;public class ReflectDemo02 {public static void main(String[] args) {// Class中定义了getClass方法,Class是所有类的父类,故每个类都有getClass方法// 每个类都可以由实例调用getClass方法得到该类的字节码文件(该类的类型)String str = "enqingshan";Class<? extends String> aClass = str.getClass();// clazz就代表String类的字节码文件System.out.println("java.lang.String类型: " + aClass);}}
class属性
package priv.yqs;public class ReflectDemo03 {public static void main(String[] args) {// 任何类型包括基本数据类型都有class属性,任何类型都可以通过class属性得到该类型的类型Class<Integer> aClass = int.class;// clazz就代表Integer类的字节码文件System.out.println("java.lang.Integer类型: " + aClass);}}
通过反射得到实例
package priv.yqs;public class ReflectDemo04 {private String name;private int age;private String gander;public ReflectDemo04() {System.out.println("priv.yqs.ReflectDemo04无参构造器被调用");}public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {// 获取ReflectDemo04类型的类型Class<?> aClass = Class.forName("priv.yqs.ReflectDemo04");// newInstance()方法底层会调用字节码对象中的无参构造器来实例化对象// 通过字节码对象反射获得ReflectDemo04实例ReflectDemo04 reflectDemo04 = (ReflectDemo04) aClass.newInstance();}}
场景一
在不更改源代码条件下,近修改配置文件便可以得到不同类的实例
className=priv.yqs.ReflectDemo05
package priv.yqs;import java.util.ResourceBundle;public class ReflectDemo05 {public ReflectDemo05() {System.out.println("priv.yqs.ReflectDemo05无参构造器被调用");}public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {// 获取配置文件内容ResourceBundle res = ResourceBundle.getBundle("className");// 获取ReflectDemo04类型的类型Class<?> aClass = Class.forName(res.getString("className"));// newInstance()方法底层会调用字节码对象中的无参构造器来实例化对象// 通过字节码对象反射获得ReflectDemo04实例ReflectDemo05 reflectDemo05 = (ReflectDemo05) aClass.newInstance();}}
场景二
只想使用某些类中定义的静态代码块,可以使用forName()方法加载类,此时该类只被加载,而不会被实例化
package priv.yqs;import java.util.ResourceBundle;public class ReflectDemo06 {static {System.out.println("priv.yqs.ReflectDemo05中静态代码块被执行");}public ReflectDemo06() {System.out.println("priv.yqs.ReflectDemo05无参构造器被调用");}public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException {// 获取配置文件内容ResourceBundle res = ResourceBundle.getBundle("className");// 获取ReflectDemo04类型的类型Class<?> aClass = Class.forName(res.getString("className"));}}// 只会打印:priv.yqs.ReflectDemo05中静态代码块被执行
获取类路径
public class ReflectDemo07 {public static void main(String[] args) throws IOException {// 获取类路径下文件绝对路径String path = Objects.requireNonNull(Thread.currentThread().getContextClassLoader().getResource("className.properties")).getPath();System.out.println("path = " + path);InputStream is = Thread.currentThread().getContextClassLoader().getResourceAsStream("className.properties");Properties prop = new Properties();prop.load(is);Object className = prop.get("className");System.out.println("className = " + className);}}
ResourceBundle
public class ReflectDemo07 {public static void main(String[] args) throws IOException {// 文件在类路径下// 扩展名必须是properties// getBundle("className")不加扩展名ResourceBundle rsb = ResourceBundle.getBundle("className");String rsbString = rsb.getString("className");System.out.println("rsbString = " + rsbString);}}
