Java学习(十二)
文章目录
类的加载机制与反射
-
类的加载
类也是一种对象,是
java.lang.Class
实例;类的初始化过程:加载、连接、初始化过程:类的加载由类加载器(默认由JVM提供)完成,指的是加载
.class
文件,这个文件可以是来源于JAR包,加载完毕后,系统会生成Class
对象。 -
类的连接
该阶段负责把类的二进制数据合并到JRE中。
过程:
-
验证:验证加载的类的内部结构是否正确
-
准备:为类的类变量(
static
)分配内存,并设置默认初始值 -
解析:将类的二进制数据中的符号引用替换成直接引用(把符号转换为真实地址)
符号引用 :符号引用以一组符号来描述所引用的目标。符号引用可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可,符号引用和虚拟机的布局无关。个人理解为:在编译的时候一个每个java类都会被编译成一个class文件,但在编译的时候虚拟机并不知道所引用类的地址,多以就用符号引用来代替,而在这个解析阶段就是为了把这个符号引用转化成为真正的地址的阶段。
直接引用 :直接引用和虚拟机的布局是相关的,不同的虚拟机对于相同的符号引用所翻译出来的直接引用一般是不同的。如果有了直接引用,那么直接引用的目标一定被加载到了内存中。
直接引用可以是:
1:直接指向目标的指针。
2:相对偏移量。
3:一个间接定位到对象的句柄。
-
-
类的初始化
对于
final
类型的变量,如果在编译阶段就能确定下来,相当于宏变量,初始化阶段不会初始化相应的类和值。初始化的时刻:
- 创建实例
- 调用静态方法
- 访问类或接口的类变量(静态变量)
- 初始化子类
- 直接用java命令运行主类
过程:
- 初始化类的直接父类
- 依次执行初始化语句
-
类加载器
JVM启动时,会形成三个类加载器组成的初始类加载器层次结构
Bootstrap ClassLoader
、Extension ClassLoader
、System Classloader
- 根类加载器由JVM自身实现,负责加载Java核心类
- 扩展类加载器用于加载JRE扩展目录(
%JAVA_HOME%/jre/lib/ext
)中的JAR包的类,为Java扩展功能可以把自己的JAR包放入此路径 - 系统类加载器负责在JVM启动时,加载
java
命令的-classpath
选项和CLASSPATH
环境变量指定的JAR包和类路径,可通过ClassLoader.getSystemClassLoader()
获取系统类加载器,默认自定义类的加载器以系统类加载器作为父加载器。
-
通过反射查看类的信息
Java具有编译时类型和运行时类型两种不同时刻的类型,因此有时候需要知道运行时的具体类型。
一般有两种做法:
- 在编译和运行的时候都完全知道类型具体信息,利用
instanceof
和强制类型转换将其转换成运行时的具体类型变量 - 只能通过运行时的信息来发现该对象和类的真实信息,只能利用反射
获得
Class
对象三种方式:
Class.forName(String clazzName)
,通过类的完整名称来获取其Class对象XxxClass.class
属性得到一个类的对象Obj.getClass()
Class类提供了查看类的详细信息的方法
-
获取构造器
如
Constructor<T> getConstructors()
等 -
获取方法
Method[] getMethods()
等如果出现方法重载,那么则需要知道形参的情况,Java8提供的Executable抽象基类派生了Constructor和Method两个子类,使得能够通过
int getParameterCount()
和Parameter[] getParameters()
获取构造器或方法的形参;javac
命令编译java程序时,默认生成的.class
文件不包含形参名 -
获取成员变量
Field[] getFields()
等 -
获取Annotation
Annotation[] getAnnotations()
等,但有些注释只能保留在源码上,无法通过反射访问 -
获取内部类
Class<?>[] getDeclaredClasses()
等 -
获取修饰符、所在包、类名等信息
int getModifiers()
返回修饰符,需要用Modifier
工具类来解码 -
判断类是否为接口、枚举、注解类型
boolean isInterface()
- 在编译和运行的时候都完全知道类型具体信息,利用
-
通过反射创建并操作对象
创建对象
Class
对象的newInstance()
方法,直接调用类的默认构造器- 先使用
Class
对象来获取指定的Constructor
对象,调用Constructor
对象的newInstance()
方法
调用方法
通过
Method
对象的Object invoke(Object obj, Object ... args)
来操作,其中obj
是执行该方法的对象,后面的args
是执行该方法时传入的实参,返回值就是方法的返回值。调用
private
方法时,可以先通过setAccessible(boolean flag)
来设定语言访问权限检查,为true
时取消访问权限检查访问成员变量
Field
类提供了如下两组方法来读取或设置成员变量的值getXxx(Object obj)
:此处Xxx对应8种基本类型,如果为引用变量则不要Xxx,obj
代表的是实例对象setXxx(Object obj, Xxx val)
:此处Xxx对应8种基本类型,如果为引用变量则不要Xxx,obj
代表的是实例对象操作数组
java.lang.reflect.Array
类可以操作数组,Array
对象能够代表所有的数组。有三种基本方法:static Object newInstance(Class<?> componentType, int... length)
:创建一个具有指定的元素类型、指定维度的新数组(可指定多维数组)static xxx getXxx(Object array, int index)
:返回array
数组中第index
个元素。xxx是基本类型,引用类型则get后面的Xxx消去static void setXxx(Object array, int index, xxx val)
:同理如上
-
动态代理
Java可以采用
Proxy
类的动态代理方式,代理执行特定类的方法,同时插入或新增一些别的方法(比如默认方法),大概就是用在框架底层, 用代理执行类把传入的对象包一层, 然后代理执行类本身带有一些他自己定好的方法, 就相当于是一个插槽,把自己的类放进去,执行,但是带有一些框架本身通用的方法。(AOP代理,面向切面编程) -
反射与泛型
在反射的
Class
类中,比如String.class
的类型实际上是Class<String>
,如果类型未知,可使用类型通配符来替代。在反射中使用泛型可以避免强制类型转换后,在运行中抛出异常,这样可以在编译阶段就检查出错误。使用反射获取泛型信息步骤
- 通过对象实例的
getGenericType()
获取Type
对象 - 将Type对象强制转换为
ParameterizedType
(被参数化的类型) ParameterizedType
提供了getRawType()
(泛型信息原始类型,返回值类型是Type
)和getActualTypeArguements()
(泛型参数类型,返回值是Type[]
类型)
- 通过对象实例的