视为动态语言的关键,反射机制允许程序在运行期间借助与Reflection API取得任何类的内部信息,并能直接操作任意对象的内部属性及方法
加载完类之后,在堆内存的方法区中生成一个Class类型的对象,这个对象就包含了完整的类的结构信息
可以调用类中的私有结构
提供的功能
在运行时判断任意一个对象所属的类
在运行时构造任意一个类的对象
在运行时判断任意一个对象所具有的成员变量和方法
在运行时获取泛型信息
在运行时调用任意一个对象的成员变量和方法
在运行时处理注解
生成动态代理
优点
提高了程序的灵活性和扩展性,降低了耦合性,提高自适应能力
允许程序创建和控制任何类的对象,无需提前硬编码目标类
缺点
反射的性能较低
反射机制主要应用在对灵活性或扩展性要求很高的系统框架上
反射会模糊程序内部逻辑,可读性较差
Class类理解
反射的源头
针对于编写的.java源文件编译(使用javac.exe),会生成一个或多个.class字节码文件
使用java.exe对指定的.class文件进行解释运行,这个解释运行的过程中,我们需要将.class字节码文件加载到内存中(方法区)。加载到内存中的.class文件对应的结构即为Class的一个实例
说明
运行时类在内存中会缓存起来,在整个执行期间,同一个类加载器只会加载一次
所有java类型都有Class实例
对于数组的Class实例,只有当元素类型与维度(几维数组)一样时,才是同一个Class
获取Class对象
// 获取Class对象(不同方式获取的Class对象是同一个)
// 类名.class 例如String.class
// 对象.getClass()
// className 包含包名的全类名
Class.from(String className)
// 使用类加载器获取,className 包含包名的全类名
ClassLoader.getSystemClassLoader().loadClass(String className)
类的加载过程
过程1 Loading 装载
将类的class文件读入内存,并为之创建一个java.lang.Class对象,此过程由类加载器完成
过程2 Linking 链接
Verify验证
确保加载的类的信息符合JVM规范,例如,以cafebabe开头,没有安全方面问题
Prepare准备
正式为类变量(static)分配内存并设置类变量默认初始值的阶段,这些内存都将在方法区进行分配
Resolve解析
虚拟机常量池内的符号引用(常量名)替换为直接引用(地址)的过程;将常量加载到内存中,并标记内存地址
过程3 Initialization 初始化
执行类构造器<clinit>()方法的过程
类构造器<clinit>()方法是由编译期自动收集类中所有变量的赋值动作和静态代码块中的语句合并产生的
类的加载器(JDK8为例)
作用
负责类的加载,并对应于一个Class的实例
分类
引导类加载器
BootstrapClassLoader:引导类加载器、启动类加载器
使用C/C++实现,不能通过java代码获取其实例
负责加载java的核心库(JAVA_HOME/jre/rt.jar或sun.boot.class.path路径下的内容)
继承于ClassLoader的类加载器
ExtensionClassLoader:扩展类加载器
java语言编写,由sum.misc.Launcher$ExtClassLoader实现
负责加载java.ext.dirs系统属性所指定的目录中加载类库,或从JDK的安装目录的jre/lib/ext子目录下还在类库
SystemClassLoader/ApplicationClassLoader:系统类加载器、应用程序类加载器
我们自定义的类,默认使用的类的加载器
用户自定义类的加载器
实现应用隔离,同一个类在一个应用程序中可以加载多份
可以实现数据加密
使用类加载器加载指定的配置文件
public void test() {
Properties properties = new Properties();
// 通过类的加载读取的文件的默认路径为当前module下的src下;不能使用..的相对路径
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("info.properties");
properties.load(is);
properties.getProperty("key");
}
类加载器使用双亲委托机制,保证类加载器的正确调用
Class方法
// 获取一个被反射的类的对象,要求类中必须提供一个空参的构造器,且权限要足够;jdk9之后废弃,使用Constructor类获取类实例
newInstance();
// 获取指定的public的属性
Field getField(String str)
// 获取运行时类本身及其所有父类中声明为public的属性
Field[] getFields()
// 获取指定的属性,可以获取private的属性
Field getDeclaredField(String str)
// 获取运行时类的所有属性,可以获取private的属性
Field[] getDeclaredFields()
// 获取指定的public的方法
Method getMethod(String str)
// 获取运行时类本身及其所有父类中声明为public的方法
Method[] getMethods()
// 获取指定的方法,可以获取private的方法
Method getDeclaredMethod(String str)
// 获取运行时类的所有方法,可以获取private的方法
Method[] getDeclaredMethods()
// 获取指定参数类型的构造器,参数为构造器形参的类型的Class类,例如:String.class int.class
Constructor getDeclaredConstructor(Class ... c)
// 获取数据类型
String getName()
// 获取父类,不带泛型
Class getSuperclass()
// 获取父类,带泛型
Type getGenericSuperclass()
// 获取实现的接口
Class[] getInterfaces()
// 获取所在的包
Package getPackage()
// 获取类的指定注解,注解的声明周期必须是RUNTIME的
Annotation getDeclaredAnnotation(Class c)
获取父类的泛型
public void test() {
Class clazz = Class.forName("")
Type superClass = clazz.getGenericSuperclass();
如果父类是带泛型的,则可以强转
ParameterizedType paramType = (ParameterizedType) superClass;
// 返回一个泛型参数的数组,可以是多个泛型
Type[] arguments = paramType.getActualTypeArguments();
}
Field类
// 设置指定属性的值,处理静态变量时c指定为类名.class或null(因为已经知道是从那个类获取到的)
set(Class c, Obejct o)
// 获取指定属性的值,处理静态变量时c指定为类名.class或null(因为已经知道是从那个类获取到的)
get(Class c)
// 设置允许访问私有属性
setAccessible()
// 获取修饰符的数字映射
int getModifiers()
// 获取属性数据类型
Class getType()
// 获取属性名
String getName()
// 获取属性的指定注解,注解的声明周期必须是RUNTIME的
Annotation getDeclaredAnnotation(Class c)
Method类
// 执行指定的方法,返回方法结果;如果方法返回值是void,则invoice的返回值为null;执行静态方法时c指定为类名.class或null(因为已经知道是从那个类获取到的)
Object invoice(Class c, Object ... args)
// 设置允许访问私有方法
setAccessible()
// 获取方法注解,注解的声明周期必须是RUNTIME的
Annotation[] getAnnotations()
// 获取修饰符的数字映射
int getModifiers()
// 获取返回值类型
Class getReturnType()
// 获取方法名
String getName()
// 获取形参类型列表
Class[] getParameterTypes()
// 获取抛出的异常列表
Class[] getExceptionTypes()
Constructor类
// 设置允许访问私有构造器
setAccessible()
// 执行构造器
Object newInstance(Object ... initargs)
Modifier类
// 获取数字对应的权限修饰符
static toString(int m)
Annotation类
可以获取注解的属性、属性值
Type类
Package类
注意
通过反射。可以调用类中私有的结构,是否与面向对象的封装性冲突
封装性:体现的是是否建议我们调用内部api的问题。比如,private声明的结构,意味着不建议调用
反射:体现的是我们能否调用的问题。因为类的完整结构都加载到了内存中,所以我们就有能力进行调用