1. 类加载器
1.1. 相关概念
- 类加载的描述
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的加载,类的连接,类的初始化这三个步骤来对类进行初始化.如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化
- 类的加载
- 就是指将class文件读入内存,并为之创建一个 java.lang.Class 对象
- 任何类被使用时,系统都会为之建立一个 java.lang.Class 对象
- 类的连接
- 验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致
- 准备阶段:负责为类的类变量分配内存,并设置默认初始化值
- 解析阶段:将类的二进制数据中的符号引用替换为直接引用
- 类的初始化
- 在该阶段,主要就是对类变量进行初始化
- 类的初始化步骤
- 假如类还未被加载和连接,则程序先加载并连接该类
- 假如该类的直接父类还未被初始化,则先初始化其直接父类
- 假如类中有初始化语句,则系统依次执行这些初始化语句
- 注意:在执行第2个步骤的时候,系统对直接父类的初始化步骤也遵循初始化步骤1-3
- 类的初始化时机
- 创建类的实例
- 调用类的类方法
- 访问类或者接口的类变量,或者为该类变量赋值
- 使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
- 初始化某个类的子类
- 直接使用java.exe命令来运行某个主类
1.2. 类加载器
负责将.class文件加载到内存中,并为之生成对应的 java.lang.Class 对象.虽然我们不用过分关心类加载机 制,但是了解这个机制我们就能更好的理解程序的运行!
1.2.1. JVM的类加载机制
- 全盘负责:就是当一个类加载器负责加载某个Class时,该Class所依赖的和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入
- 父类委托:就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类
- 缓存机制:保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制数据,并将其转换成Class对象,存储到缓存区
1.2.2. Java的内置加载器
从上到下是父子关系.
- Bootstrap class loader:它是虚拟机的内置类加载器,通常表示为null ,并且没有父null
- Platform class loader:平台类加载器可以看到所有平台类 ,平台类包括由平台类加载器或其祖先定义的JavaSE平台API,其实现类和JDK特定的运行时类
- System class loader:它也被称为应用程序类加载器 ,与平台类加载器不同. 系统类加载器通常用于定义应 用程序类路径,模块路径和JDK特定工具上的类
方法名 | 说明 |
---|---|
static ClassLoader getSystemClassLoader() | 返回用于委派的系统类加载器 |
ClassLoader getParent() | 返回父类加载器进行委派 |
//static ClassLoader getSystemClassLoader():返回用于委派的系统类加载器
ClassLoader c = ClassLoader.getSystemClassLoader();
System.out.println(c); //AppClassLoader
//ClassLoader getParent():返回父类加载器进行委派
ClassLoader c2 = c.getParent();
System.out.println(c2); //PlatformClassLoader
ClassLoader c3 = c2.getParent();
System.out.println(c3); //null
2. 反射
是指在运行时去获取一个类的变量和方法信息.然后通过获取到的信息来创建对象,调用方法的一种机制.由于这种动态性,可以极大的增强程序的灵活性,程序不用在编译期就完成确定,在运行期仍然可以扩展
- 首先Teacher类通过类加载器生成一个class文件
- 通过class这个类的对象来使用成员变量,而不是Teacher类来使用
2.1. 获取Class类型对象
- 类名.class属性
- 对象名.getClass()方法
- Class.forName(全类名,即完整包名的路径)方法
Class<Actor> c1 = Actor.class;
System.out.println(c1);
Class<Actor> c2 = Actor.class;
System.out.println(c1 == c2);
System.out.println("--------");
Actor s = new Actor("你好");
Class<? extends Actor> c3 = s.getClass();
System.out.println(c1 == c3);
System.out.println("--------");
Class<?> c4 = null;
c4 = Class.forName("Actor");
System.out.println(c1 == c4);
2.2. 获取构造方法
方法名 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 返回所有公共构造方法对象的数组 |
Constructor<?>[] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
Constructor< T> getConstructor(Class<?>… parameterTypes) | 返回单个公共构造方法对象 |
Constructor< T> getDeclaredConstructor(Class<?>… parameterTypes) | 返回单个构造方法对象 |
T newInstance(Object…initargs) | 根据指定的构造方法创建对象 |
2.3. 获取成员变量
方法名 | 说明 |
---|---|
Field[] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field getField(String name) | 返回单个公共成员变量对象 |
Field getDeclaredField(String name) | 返回单个成员变量对象 |
2.4. 获取成员方法
方法名 | 说明 |
---|---|
Method[] getMethods() | 返回所有公共成员方法对象的数组,包括继承的 |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,不包括继承的 |
Method getMethod(String name, Class<?>… parameterTypes) | 返回单个公共成员方法对象 |
Method getDeclaredMethod(String name, Class<?>… parameterTypes) | 返回单个成员方法对象 |
Objectinvoke(Object obj,Object… args) | 调用obj对象的成员方法,参数是args,返回值是Object类型 |
2.5. 以上的例子
public class Actor {
private String name;
private int age;
public Actor() {
}
public Actor(String name) {
this.name = name;
}
private Actor(int age) {
this.age = age;
}
Actor(String name, int age) {
this.age = age;
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Actor{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
public class Test {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchMethodException, NoSuchFieldException {
Class<?> c = Class.forName("Actor");
Constructor<?>[] cons = c.getConstructors();//public和无参的
Constructor<?>[] cons2 = c.getDeclaredConstructors();//所有的
Constructor<?> con = c.getConstructor();
Object obj = con.newInstance();
System.out.println(obj);
Constructor<?> con2 = c.getDeclaredConstructor(String.class, int.class);
Object obj2 = con2.newInstance("张曼玉", 100);
System.out.println(obj2);
Constructor<?> con3 = c.getDeclaredConstructor(int.class);//这个是private的
con3.setAccessible(true);
Object obj3 = con3.newInstance(50);
System.out.println(obj3);
Field[] f = c.getDeclaredFields();//所有的, 同理直接get是public的
Field nameField = c.getDeclaredField("name");
nameField.setAccessible(true);
nameField.set(obj3, "加上名字");
System.out.println(obj3);
Method m = c.getMethod("getName");//getMethods包括继承的方法
System.out.println(m.invoke(obj3));
}
}
2.5.1. 跳过类型检查
public static void main(String[] args) throws Exception {
//创建集合
ArrayList<Integer> array = new ArrayList<Integer>();
array.add(10);
// array.add(20);
// array.add("hello");
Class<? extends ArrayList> c = array.getClass();
Method m = c.getMethod("add", Object.class);
m.invoke(array, "hello");//跳过了类型检查
m.invoke(array, "world");
m.invoke(array, "java");
System.out.println(array);
}
2.5.2. 通过配置文件来随时修改
public class Test {
public static void main(String[] args) throws Exception {
//加载数据
Properties prop = new Properties();
FileReader fr = new FileReader("./class.txt");
prop.load(fr);
fr.close();
/* class.txt
className=Actor
methodName=getName
*/
String className = prop.getProperty("className");
String methodName = prop.getProperty("methodName");
//通过反射来使用
Class<?> c = Class.forName(className);//
Constructor<?> con = c.getDeclaredConstructor(String.class, int.class);
Object obj = con.newInstance("黄旭东",40);
Method m = c.getMethod(methodName);//study
System.out.println(m.invoke(obj));
}
}
2.6. 反射加强
- 框架:半成品软件.可以在框架的基础上进行软件开发,简化编码
- 反射:将类的各个组成部分封装为其他对象,这就是反射机制
使用配置文件.
- 源代码阶段: xxx.java编译成xxx.class字节码,其中包含了各种信息
Class.forName("完整类名")
:将字节码文件加载到内存,返回Class类对象 - Class类对象: ClassLoader类加载器把xxx.class加载到内存成为Class类对象,该类对象有:成员变量–>Field对象数组,成员方法–>Method对象数组,构造方法–>Constructor对象数组
反射: 把类成员变量封装为Fields对象数组
- 好处:
- 可以在程序运行过程中,操作这些对象.
- 可以解耦,提高程序的可扩展性.
- 运用:
比如说,IDE的提示,“abc"字符串加个点就会有各种提示.实现就是String的Class加载到内存,String的Class类对象中有Method对象数组, 把这些方法列出来即可.
类名.class
:已经加载到内存后,直接用class属性 - 好处:
- 运行时阶段: 比如
new Person()
, 就是通过上述的Class类对象类创建对象.getClass()
:已经有对象了,就使用Object类中的方法.
并且以上三种方法获取的对象是一样的
因为同一个字节码文件(*.class)在一次程序运行过程中,只会被加载一次,不论通过哪一种方式获取的Class对象都是同一个.
3. 模块化
Java 9正式的推出了模块化系统.Java被拆分为N多个模块,并允许Java程序可以根据需要选择加载程序必须的Java模块,这样就可以让Java以轻量化的方式来运行
例子: