类加载
- 类加载指的是将类的class文件读入内存,并为之创建一个java.lang.Class对象
- 当一个变量被修饰成static final,并在编译器就能确定其值,也就是常量,那么通过类名.变量名访问该变量时不会加载该类(也就是访问常量并不会初始化这个类)
类初始化时机
类从加载到虚拟机内存开始,到卸载出内存为止,真个生命周期包括:加载(Loading),验证(Verification),准备(Preparation),解析(Resolution),初始化(Initialization),使用(Using)和卸载(Unloading)七个阶段。其中,验证、准备、解析三个部分统称为连接(Linking)
1. new对象、调用类的静态方法或者静态变量时
2. 虚拟机启动时,加载用户指定的main方法的所在类
3. 通过反射调用某个类
4. 初始化一个类时先初始化它的父类
5. 初始化某个类的子类
中心思想:初始化时机为该类首次主动使用
以下情况不会初始化
1、通过子类类名调用父类静态代码,不会触发子类的初始化。
2、通过数组来创建对象不会触发此类的初始化。
3、通过调用静态常量不会触发初始化。
类加载器
1. Bootstrap ClassLoader,被称为引导(也称为原始或根)类加载器。它负责加载 Java的核心类。在Sun的JVM中,当执行java.exe的命令时使用-Xbootclasspath选项或使用-D选项指定sun.boot.class.path系统属性值可以指定加载附加的类。根类加载器非常特殊,它并不是java.lang.ClassLoader的子类,而是由JVM自身实现的。
2. Extension ClassLoader,被称为扩展类加载器,它负责加载JRE的扩展目录(JAVA_HOME/jre/lib/ext或由java.ext.dirs系统属性指定的目录)中的JAR的类包。
3. System ClassLoaser,被称为系统(也称为应用)类加载器,它负责在JVM启动时,加载来自命令java中的-classpath选项或java.class.path系统属性,或CLASSPATH环境变量所指定的JAR包和类路径。
反射
概念
官方解释:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
简单理解
:反射就是直接通过字节码文件class,去使用它的变量,方法,和构造
使用步骤
- 获得class对象
- 从class中获取信息
获取class对象
共有三种方式:
- 使用Class类的forName(类的全名),该方法需要传入参数,参数为某个类的全名(包名+类名)
Class.forName("com.test.User");
- 调用某个类的class属性获取其对应的Class对象
User.class;
- 调用某个对象的getClass()方法
User.getClass();
从Class中获取信息
获取对应类中的构造器,类型为Constructor
- 获得所有public修饰的构造方法
public Constructor[] getConstructors();
- 获得所有构造方法,包括私有
public Constructor[] getDeclaredConstructors();
- 获取指定的public修饰构造方法,参数是该构造方法里参数的类型的class对象
public Constructor getConstructor(Class<?>... parameterTypes);
例如:
- 获取指定构造方法,包括私有
public Constructor getDeclaredConstructor(Class<?>... parameterTypes);
使用:拿到构造方法后,用Constructor对象调用newInstance()方法就可以调用该构造方法创建对象。例如
获得私有构造方法也可以使用,在newInstance操作前设置一下构造方法的访问权限:
constructor.setAccessible(true);
获取方法,类型为Method
- 获取指定的public方法
public Method getMethod(String methodName ,Class<?>… parameterTypes) //第一个参数为方法名,后面参数为形参的类型的class
2. 获取所有public方法
public Method[] getMethods()
3. 获取指定的方法,包括私有
public Method getDeclaredMethods(String name ,Class<?>… parameterTypes)
4. 获取所有方法,包括私有
public Method[] getDeclaredMethods()
使用:invoke(对象名,实参)
获取变量,类型为Field
- 获取指定的public变量
public Field getField(String name)
- 获取所有public变量
public Field[] getFields()
- 获取指定的变量,包括私有
public getDeclaredField(String name)
- 获取所有变量,包括私有
public Field[] getDeclaredFields()
使用:set(对象,值):设置属性;get(对象):获取属性值
结合配置文件的使用
操作数组
可以通过java.lang.reflect下的Array类来操作数组
- static Object new Instance(Class<?> componentType,int... length):创建一个具有指定的元素类型、指定维度的数组
- static xxx get(Object array,int index)
- static void set(Object array,in index,xxx value)
动态代理
- 通过Proxy创建动态代理对象
public static Object newProxyInstance(ClassLoader loader,Class<?>[] interfaces,InvocationHandler h) //三个参数分别为目标对象的类加载器,目标对象所有接口,实现了InvocationHandler接口的类
- InvocationHandler接口,重写invoke方法
作用:
1. 解决多个方法中调用了一个通用功能的问题
2. 解耦
举例说明:
在开发过程中,有些功能代码是可以共用的。比如有个学生类,里面有个写作业方法,写作业包含三个步骤,打开作业本,写,合上;老师类里有个批作业方法,包含三个步骤:打开作业本,批改,合上;学生类里还有个抄作业方法,包含三个步骤:打开作业本,抄作业,合上(什么鬼啊,我么都是好学生,抄什么作业啊)。
好的不要在意细节,以上例子只是想来说明一下代理的作用。对于以上的三个方法,打开和合上作业本就属于相同的代码块,于是初学者开发时为了节省时间会选择直接复制粘贴,像下面这样。
有一天,你发现需要修改红色部分的代码块,而你之前将此部分的代码块复制了好多遍,这时你发现,哇,维护起来好不方便哦。于是你想到了封装,可以把红色部分代码封装起来,在其他代码块里直接调用啊。
此时虽然解决了代码复用问题,但是却又提高了代码之间的耦合性。(开发的标准是要尽量做到高内聚,低耦合)现在的代码是这个样子:
而且调用方法的这句话也是一直在重复出现很麻烦哎
现在就让我们用动态代理来改进,所有的代理,就是帮你去做事啊,我们只需要写自己的专有方法即可,执行相同部分的代码这一步操作可以交给代理来做。
第一步,创建Student接口,包含写作业抄作业两个基本方法(jdk提供的动态代理只能做接口代理,所有要定义接口)
第二步,编写学生接口的实现类StudentImpl
第三步,实现InvocationHandler接口
第四步,编写代理工厂
第五步,测试
调用代理对象的方法时,其实走的就是重写的invoke方法,invoke方法里我们去调用了共用的方法,调用对象本身方法时就用到了反射