目录
1、类加载器
1.1、类加载
当程序要使用某个类时,如果该类还未被加载到内存中,则系统会通过类的 【加载】,【连接】,【初始化】 这三个步骤来对类进行初始化。如果不出现意外情况,JVM将会连续完成这三个步骤,所以有时也把这三个步骤统称为类加载或者类初始化。
一个类的生命周期包括了 "加载"、"验证"、"准备"、"解析"、"初始化"、"使用"、"卸载" 这七个阶段。如图所示
1.1.1、类的加载
类的加载就是指将class文件读入内存,并为之创建一个Class对象。任何类被使用时系统都会建立一个Class对象,用于加载二进制数据,类的加载主要做三件事情:
1、找到类文件(通过类的全限定名来获取定义此类的二进制字节流)
首先会根据各种途径(比如网络下载、数据库提取、从jar,zip中读取等)获取类的二进制数据
2、放入方法区(将这个字节流所代表的静态存储结构转化为方法区的运行时数据结构)
把获取到的二进制数据读入内存,存储在运行时数据区的方法区, 这些二进制数据所代表的存储结构会被转化为方法区中运行时的数据结构
3、开个入口(生成一个代表此类的java.lang.Class对象,作为访问方法区这些数据结构的入口)
在方法区中创建相应的class对象(这里的class对象与平时所说的对象是不一样的, 当使用new创建实例对象时,就会通过class对象在堆中创建实例对象)用来封装保存在方法区内的数据结构
1.1.2、类的连接
验证阶段:用于检验被加载的类是否有正确的内部结构,并和其他类协调一致。
文件格式验证:验证字节流是否符合class文件的规范,确保输入的字节流能正确解析并存储到方法区。
元数据验证:对字节码描述的信息进行语义分析,保证其描述的信息符合规范要求。 字节码验证:这个阶段是比较复杂的,通过数据流和控制流分析,对类的方法体进行校验,确保程序的合法性。
符号引用验证:这里的符号引用不单单指类的,也包括方法。 发生在符号引用转为直接引用的时候,也就是解析阶段, 对常量池中各种符号引用的信息进行匹配性校验,确保解析动作正确执行。
准备阶段:负责为类的类变量分配内存,并设置默认初始化值。
JVM会在这个阶段对类变量(静态变量,即static修饰)分配内存并设置类变量的初始值(对应数据类型的默认初始值,如0、0L、null、false等),即在方法区中分配这些变量所使用的内存空间。注意这里所说的初始值概念,比如一个类变量定义为:
public int aa = 10;
public static int bb = 66;
public static final int cc = 88;
aa不会被分配内存,而bb会分配内存,但bb的初始值是0而不是66,需要注意的是,static修饰的量是类变量,也叫做静态变量,而static final修饰的被称作为常量,所以cc的初始值是88。
解析阶段:将类的二进制数据中的符号引用替换为直接引用。
1.1.3、类的初始化
类的初始化的主要工作是为静态变量赋程序设定的初值。
1.1.4、类的初始化步骤
1、假如类还未被加载和连接,则程序先加载并连接该类
2、假如该类的直接父类还未被初始化,则先初始化其直接父类
3、假如类中有初始化语句,则系统依次执行这些初始化语句
1.1.5、类的初始化时机
创建类的实例
调用类的类方法
访问类或者接口的类变量,或者为该类变量赋值
使用反射方式来强制创建某个类或接口对应的java.lang.Class对象
初始化某个类的子类
直接使用java.exe命令来运行某个主类
1.2、类加载器
1.2.1、类加载器的作用
负责将.class文件加载到内存中,并为之生成对应的 java.lang.Class 对象。简单来说:类加载器的作用,就是把class文件装进虚拟机。
1.2.2、类加载机制
全盘负责
就是当一个类加载器负责加载某个Class时,该Class所依赖和引用的其他Class也将由该类加载器负责载入,除非显示使用另外一个类加载器来载入。
父类委托
就是当一个类加载器负责加载某个Class时,先让父类加载器试图加载该Class,只有在父类加载器无法加载该类时才尝试从自己的类路径中加载该类。
缓存机制
保证所有加载过的Class都会被缓存,当程序需要使用某个Class对象时,类加载器先从缓存区中搜索该Class,只有当缓存区中不存在该Class对象时,系统才会读取该类对应的二进制字节码文件数据,并将其转换成Class对象,存储到缓存区。
1.2.3、Java中的内置类加载器
Bootstrap ClassLoader 引导类加载器(根类加载器)
用来加载 Java 的核心库,是用原生代码来实现的, 比如:System.String等,jre的lib下rt.jar文件中。
Extension ClassLoader 扩展类加载器
由java实现 即ExtClassLoader实现类。 它主要负责加载java/lib/ext 目录和系统环境变量java.ext.dirs指定目录所有类库。
System ClassLoader 系统类加载器
它根据Java应用的类路径(CLASSPATH)来加载Java类。一般来说,Java应用的类都是由它来完成加载的。负责在jvm启动时加载来自java命令的class文件,以及classpath环境变量所指定的jar和类路径。
UserClass Loader
自定义类加载器
1.2.4、双亲委派机制
当前ClassLoader首先从自己已经加载的类中查询是否此类已经加载,如果已经加载则直接返回原来已经加载的类。每个类加载器都有自己的加载缓存,当一个类被加载了以后就会放入缓存,等下次加载的时候就可以直接返回了。
当前ClassLoader的缓存中没有找到被加载的类的时候,委托父类加载器去加载,父类加载器采用同样的策略,首先查看自己的缓存,然后委托父类的父类去加载, 一直到Bootstrp ClassLoader。
当所有的父类加载器都没有加载的时候,再由当前的类加载器加载,并将其放入它自己的缓存中,以便下次有加载请求的时候直接返回。如图所示
好处:
1、避免重复加载。当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。
2、为了安全。避免核心类,比如String被替换。
2、反射
2.1、反射概述
JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;对于这种动态的获取信息以及动态调用对象的方法的功能称为:java语言的反射机制。
反射理解: 可以通你这个对象拿到字节码文件,通过子节码文件还原到类的本身(也就是说你拿到类的Class对象去使用这个类的成员方法,成员变量,构造方法)
2.2、获取Class类对象的三种方式
类名.class属性
对象名.getClass()方法
Class.forName(全限定类名)方法
2.2.1、代码演示
首先定义一个实体类,里面定义的有公有私有以及默认访问权限的构造方法、成员方法。
public class Person {
//私有成员变量
private String name;
//默认成员变量
int age;
//公有成员变量
public String address;
//公有无参构造方法
public Person() {
}
//私有的有参构造方法 1个参数
private Person(String name) {
this.name = name;
}
//默认的有参构造方法 2个参数
Person(String name, int age) {
this.name = name;
this.age = age;
}
//公有的有参构造方法 3个参数
public Person(String name, int age, String address) {
this.name = name;
this.age = age;
this.address = address;
}
//公有的无参无返回值方法
public void show() {
System.out.println("show");
}
//公有的有参无返回值方法
public void method(String s) {
System.out.println("method " + s);
}
//公有的有参有返回值方法
public String getString(String s, int i) {
return s + "---" + i;
}
//私有的无参无返回值方法
private void function() {
System.out.println("function");
}
@Override
public String toString() {
return "Person [name=" + name + ", age=" + age + ", address=" + address
+ "]";
}
}
public class Test {
public static void main(String[] args) throws ClassNotFoundException {
//方式一 getClass()
Person p1 = new Person();
Class c1 = p1.getClass();
Person p2 = new Person();
Class c2 = p2.getClass();
System.out.println(p1 == p2);//false
System.out.println(c1 == c2);//true
//方式二 类名.class
Class c3 = Person.class;
System.out.println(c1 == c3);//true
//方式三 Class.forName()
Class c4 = Class.forName("cn.itssl.demo.Person");
System.out.println(c1 == c4);//true
}
}
通过反射获取的对象,只要是同一个类,那么它们的对象都是一样的,可以看出==比较最终都为true。
2.3、获取构造方法
方法名 | 说明 |
---|---|
Constructor<?>[] getConstructors() | 返回所有公共构造方法对象的数组 |
Constructor<?>[] getDeclaredConstructors() | 返回所有构造方法对象的数组 |
Constructor<T> getConstructor(Class<?>... parameterTypes) | 返回单个公共构造方法对象 |
Constructor<T> getDeclaredConstructor(Class<?>... parameterTypes) | 返回单个构造方法对象 |
public class Test1 {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Class c1=Person.class;
//获取public修饰的构造方法
Constructor[] constructors = c1.getConstructors();
for (Constructor constructor : constructors) {
System.out.println(constructor);
}
System.out.println("-------------------------");
//获取全部构造方法,不管私有公共
Constructor[] d= c1.getDeclaredConstructors();
for (Constructor constructor : d) {
System.out.println(constructor);
}
System.out.println("-------------------------");
//通过无参构造方法创建对象
Constructor s=c1.getConstructor();
Object o = s.newInstance();
System.out.println(o);
System.out.println("-------------------------");
//获取指定参数的有参构造方法
Constructor con = c1.getConstructor(String.class, int.class, String.class);
System.out.println(con);
Object o1 = con.newInstance("王五", 21, "河南");
System.out.println(o1);
System.out.println("-------------------------");
//获取私有的构造方法对象
Constructor con1 = c1.getDeclaredConstructor(String.class);
con1.setAccessible(true);//暴力访问,值为true则指示反射的对象在使用时应该取消Java语言访问检查
Object obj = con1.newInstance("蕾蕾");
System.out.println(obj);
}
}
2.4、获取成员方法
Class类获取成员方法对象的方法
方法名 | 说明 |
---|---|
Method[] getMethods() | 返回所有公共成员方法对象的数组,包括继承的 |
Method[] getDeclaredMethods() | 返回所有成员方法对象的数组,不包括继承的 |
Method getMethod(String name, Class<?>... parameterTypes) | 返回单个公共成员方法对象 |
Method getDeclaredMethod(String name, Class<?>... parameterTypes) | 返回单个成员方法对象 |
Object invoke(Object obj,Object... args) | 调用obj对象的成员方法,参数是args,返回值是Object类型 |
2.4.1、代码演示
public class Test2 {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Class con = Class.forName("cn.itssl.demo.Person");
Method[] declaredMethods = con.getDeclaredMethods();//获取所有方法
for (Method declaredMethod : declaredMethods) {
System.out.println(declaredMethod);
}
//获取类的构造方法
Constructor c = con.getConstructor();
//创建类对象
Object o = c.newInstance();
//根据方法名获取方法
Method m2 = con.getMethod("show");
//调用方法执行
m2.invoke(o);
System.out.println("-------------");
//获取所有方法,私有公有默认都获取
Method[] dm = con.getDeclaredMethods();
for (Method method : dm) {
System.out.println(method);
}
System.out.println("--------------");
//获取指定参数列表和方法名的方法 public String getString(String s, int i)
Method getString = con.getMethod("getString", String.class, int.class);
System.out.println(getString);
getString.invoke(o,"hello",100);
System.out.println("---------------");
// private void function() 获取私有方法
Method function = con.getDeclaredMethod("function");
function.setAccessible(true);
function.invoke(o);
}
}
2.5、获取成员变量
Class类获取成员变量对象的方法
方法名 | 说明 |
---|---|
Field[] getFields() | 返回所有公共成员变量对象的数组 |
Field[] getDeclaredFields() | 返回所有成员变量对象的数组 |
Field getField(String name) | 返回单个公共成员变量对象 |
Field getDeclaredField(String name) | 返回单个成员变量对象 |
void set(Object obj,Object value) | 给obj对象的成员变量赋值为value |
public class Test3 {
public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException, NoSuchFieldException {
Class<?> c1 = Class.forName("cn.itssl.demo.Person");
//输出所有的成员变量 不管私有公有默认
Field[] declaredFields = c1.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println(declaredField);
}
//创建实例化对象
Object o = c1.newInstance();
//获取单个成员变量
//获取address并对其赋值
Field address = c1.getField("address");
address.set(o,"北京");
System.out.println(o);
System.out.println("-----------------------");
//获取成员变量name
Field name = c1.getDeclaredField("name");
name.setAccessible(true);//暴力访问 可以直接访问私有
name.set(o,"张三");
System.out.println(o);
System.out.println("-----------------------");
//获取成员变量age
Field age = c1.getDeclaredField("age");
age.setAccessible(true);
age.set(o,20);
System.out.println(o);
}
}
3、案例-通过反射获取配置文件内容
首先创建一个properties配置文件,在项目的下一级,与src同级, 里面配置ClassName和MethodName内容。
public class Demo {
public static void main(String[] args) throws IOException, ClassNotFoundException, NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
//获取ApplicationConfig.properties配置文件,字符流缓冲
BufferedReader bis=new BufferedReader(new FileReader("Application.properties"));
//创建Properties集合对象
Properties pro=new Properties();
//加载配置文件
pro.load(bis);
//关闭流
bis.close();
//获取key值ClassName对应的value值
String className = pro.getProperty("ClassName");
String methodName = pro.getProperty("MethodName");
//利用反射获取Person类Class对象 这里的路径直接填上面从Properties获取的
Class con = Class.forName(className);
//获取构造方法
Constructor c=con.getConstructor();
//创建Person实例化对象
Object o = c.newInstance();
//获取show方法
Method show = con.getMethod(methodName);
//执行show方法
show.invoke(o);
}
}
执行结果