类的加载
java运行的都是类
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过加载,连接,初始化三步来实现这个类进行初始化。
加载
加载,是指Java虚拟机查找字节流(查找.class文件),并且根据字节流创建java.lang.Class对象的过程。这个过程,将类的.class文件中的二进制数据读入内存,放在运行时区域的方法区内。然后在堆中创建java.lang.Class对象,用来封装类在方法区的数据结构。
类加载阶段:
(1)Java虚拟机将.class文件读入内存,并为之创建一个Class对象。
(2)任何类被使用时系统都会为其创建一个且仅有一个Class对象。
(3)这个Class对象描述了这个类创建出来的对象的所有信息,比如有哪些构造方法,都有哪些成员方法,都有哪些成员变量等。
连接
链接包括验证、准备以及解析三个阶段。
验证阶段:主要的目的是确保被加载的类(.class文件的字节流)满足Java虚拟机规范,不会造成安全错误。
准备阶段:负责为类的静态成员分配内存,并设置默认初始值。
解析阶段:将类的二进制数据中的符号引用替换为直接引用。
说明:
符号引用。即一个字符串,但是这个字符串给出了一些能够唯一性识别一个方法,一个变量,一个类的相关信息。
直接引用。可以理解为一个内存地址,或者一个偏移量。比如类方法,类变量的直接引用是指向方法区的指针;而实例方法,实例变量的直接引用则是从实例的头指针开始算起到这个实例变量位置的偏移量。
举个例子来说,现在调用方法hello(),这个方法的地址是0xaabbccdd,那么hello就是符号引用,0xaabbccdd就是直接引用。
在解析阶段,虚拟机会把所有的类名,方法名,字段名这些符号引用替换为具体的内存地址或偏移量,也就是直接引用。
初始化
初始化,则是为标记为常量值的字段赋值的过程。换句话说,只对static修饰的变量或语句块进行初始化。
如果初始化一个类的时候,其父类尚未初始化,则优先初始化其父类。
如果同时包含多个静态变量和静态代码块,则按照自上而下的顺序依次执行。
类的初始化时机
1.创建类的实例
2.使用类的静态变量,或者为静态变量赋值
3.调用类的静态方法
4.使用反射强制创建某个类或接口对应的java.lang.Class对象
5.初始化某个类的子类
6.直接运行某个类
类的加载器
BootStrapLoader(根类加载器)
用来加载java的核心类,比如System,String等。这些类在jre的lib目录下rt.jar中
ExtClassLoader(扩展类加载器)
负责jre的扩展目录中的jar包加载,这些类在jre的lib目录下的ext中
AppClassLoader(系统类加载器)
负责在JVM启动时加载来自Java命令的class文件,以及classpath环境变量所制定的jar包和类路径。
加载程序员自己写的类
反射
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。
获取字节码文件对象(Class)的三种方式
1.Object中的getClass方法来获取Class对象
Object obj=new Proson();
Class clazz1 =obj.getClass();
2.直接通过 类名.class来获取Class对象
Class clazz2=Proson.class;
3.通过Class 对象的forName()静态方法来获取Class对象
Class clazz3 = Class.forName("com.xiaoli.bean.Proson");
一个类在 JVM 中只会有一个 Class 实例
泛型擦除
编译后产生的.class文件没有泛型约束,这种现象称为泛型擦除
反射展示泛型擦除效果
ArrayList arr = new ArrayList();
arr.add("a");
//反射获取ArrayList类的class文件对象
Class c = arr.getClass();
Method method = c.getMethod("add",object.class);
//实现向String类型的ArrayList中加入整型
method.invock(array,1)
通过配置文件更改运行内容
public class Person {
public void eat(){
System.out.println("人在吃饭");
}
}
public class Student {
public void study(){
System.out.println("学生在学习");
}
}
public class Worker {
public void job(){
System.out.println("上班族在工作");
}
}
public class Test {
public static void main(String[] args) throws Exception{
//IO流读取配置文件
FileReader r = new FileReader("config.properties");
//创建集合对象
Properties pro = new Properties();
//调用集合方法load,传递流对象
pro.load(r);
r.close();
//通过键获取值
String className = pro.getProperty("className");
String methodName = pro.getProperty("methodName");
//反射获取指定类的class文件对象
Class c = Class.forName(className);
Object obj = c.newInstance();
//获取指定的方法名
Method method = c.getMethod(methodName);
method.invoke(obj);
}
}
配置文件:
className=cn.itcast.demo3.Student
methodName=study
className=cn.itcast.demo3.Person
methodName=eat
className=cn.itcast.demo3.Worker
methodName=job
使用时注释掉不需要的即可