当程序主动使用某个类时,如果该类还未被加载到内存中,则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);
}
}