java反射
0、java在计算机的三个阶段
一、反射
1、反射快速入门
public class Test1 {
public static void main(String[] args) {
//加载资源文件
Properties properties = new Properties();
try {
properties.load(new FileInputStream("D:\\IDEA\\maven_code\\mavenProject\\ReflectionTest\\src\\person.properties"));
//获取类的路径
String classpath = properties.get("classpath").toString();
//获取类的方法
String method = properties.get("method").toString();
//测试
System.out.println(classpath);
//通过反射创建person类的Class对象
Class aClass = Class.forName(classpath);
//通过aClass对象实例化Person
Object o1 = aClass.newInstance();
Object o2 = aClass.newInstance();
System.out.println("实例化的类是否相等" + o1.equals(o2));
//通过aClass对象获取方法
Method method1 = aClass.getMethod(method);
//调用方法-》 方法.invoke(类名)
method1.invoke(o1);
} catch (Exception e) {
e.printStackTrace();
}
}
}
2、反射优缺点
优点:
增加程序的灵活性,避免将程序写死到代码里
代码简洁,提高代码的复用率,外部调用方便
对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法
缺点;
1、性能问题
使用反射基本上是一种解释执行,用于字段和方法接入时要远慢于直接代码。因此Java反射机制主要应用在对灵活性和扩展性要求 很高的系统框架上,普通程序不建议使用。
反射包括了一些动态类型,所以JVM无法对这些代码进行优化。因此,反射操作的效率要比那些非反射操作低得多。我们应该避免 在经常被执行的代码或对性能要求很高的程序中使用反射。
2、安全限制
3、内部暴露
2.1、反射优化
在反射调用方法时,取消访问检查(可提升一点速度)
//方法.setAccessible(ture)
//true为取消检查
2.2、解释执行与编译执行
解释执行描述的是将源语言直接作为输入,每次执行一条就将源代码解释成机器码,并不保留中间结果。因为每次都需要解释所以移植性和跨平台性很高。
编译执行描述的是将源代码事先编译成目标机器的机器码文件,这样一来就直接可以在目标机器上运行,但是由于目标机器的不同,每次更换目标都需要重新编译与之相对的机器码文件,所以移植性较差。
3、Class类
3.1 基本介绍
- Class也是一个类,因此继承Object类
- Class类对象不是new出的,是Java虚拟机就会自动产生一个Class对象。
- 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
- 每个类的实例知道自己由那个Class所生成
- 通过Class对象的API可以完整的获得一个类的结构
- Class对象是存在堆中的
- 类的字节码二进制数据,放在方法区中(称为类的元数据,包括方法代码,变量名,方法名,访问权限等)
3.2 常用方法
方法 | 作用 |
---|---|
getName() | 以 String 的形式返回此 Class 对象所表示的实体 |
aClass.getPackage().getName() | 返回包名 |
aClass.getName() | 返回全类名 |
newInstance() | 产生其对应类的实例,它调用的是此类的默认构造方法 |
getField("。。") | 获取属性 |
getFields() | 获取所有属性 |
3.3 获取Class对象的方式
1.Class.forName();
读取配置文件时常用(编译阶段)
2.类名.class
用于参数传递(加载阶段)
3.对象.getClass()
有对象实例时(运行阶段)
4.对象.getClass().getClasLoader().loadClass("..")
类加载器获取
5.基本数据类型.class / .TYPE
int.class/int.TYPE
3.4 那些数据类型有Class对象
外部类,接口,数组,二维数组,注解,枚举,基本类型,void,class
4、类加载
1.基本说明
- 静态加载:编译时加载相关类,如果没有则报错,依赖性太强
- 创建对象时
- 子类被加载,父类也加载
- 调用类中的静态成员时
- 动态加载:运行时加载需要的类,如果运行时没用到,则不会报错,降低依赖性(反射使用动态加载)
- 反射
2 类加载过程
类从被加载到虚拟机内存开始,到卸载出内存为止,它的整个生命周期包括:加载、验证、准备、解析、初始化、使用和卸载7个阶段。其中验证、准备、解析三个阶段称之为连接。
加载:获取定义此类的二进制字节流。
将这个字节流所代表的静态存储结构转换为方法区的运行时数据结构
在内存中共生成一个代表这个类的java.lang.Class对象
验证:为了确保Class文件字节流中包含的信息符合当前虚拟机的要求,并且不会威胁虚拟机自身的安全。其实如果纯粹是从Java源码编译得到的Class文件,自身是可以确保安全的,但是因为Class文件可以由任何途径产生(甚至可以由十六进制编辑器直接编写来产生Class文件),所以虚拟机很有必要对输入的字节流进行验证以维护自身的安全。
验证阶段需要完成四个阶段的校验动作:文件格式验证(基于二进制字节流)、元数据验证(基于方法区的存储结构)、字节码验证(基于方法区的存储结构)、符号引用验证()。也可以关闭安全验证
准备:对静态变量默认初始化,并分配空间(这里的初始值不是代码中指的初始值而是变量数据类型对应的零值(int为0,String为null等)。
解析:把符号引用转为直接引用
初始化:真正开始执行类中定义的Java程序代码,执行静态变量的初始化赋值
此阶段执行**<clinit>()方法**的过程:按照编译器**语句**在源文件出现的**顺序**,依次收集类中的所有**静态变量的赋值**和**静态代码块**中的语句,并进行**合并**(在多线程环境中被正确的**加锁、同步**)
(因为有加锁的机制,才保证了类在内存中只有一份Class对象)
5、获取类结构信息
1 .java.lang.Class类
2.java.lang.Field
Field.getModifiers()
修饰符的值:
默认 0
public 1
private 2
protected 4
static 8
final 16
组合 相加
3.java.lang.Method/java.lang.Constructor
getReturnType():得到方法返回值的class类型
getParameterTypes():形参数组情况
6、通过反射创建对象
暴破:使用反射可以访问private构造器
setAccessible(true)
1、默认无参构造一个对象
class.newInstance()
2、使用public构造器构造对象,
先获取构造器(getConstructors()),再constructor.newInstance(“实参”)
3、使用private构造器获取对象
先获取构造器(getDeclaredConstructors()),再使用暴破(constructor.setAccessible(true)),最后constructor.newInstance(“实参”)