十六、反射(reflection)
反射可以通过外部文件配置,在不修改源码的情况下来控制程序,符合设计模式中的OCP原则(开闭原则:不修改源码,扩容功能)。
1、反射机制
(1)基本介绍
- 反射机制允许程序在执行期间借助Reflection API 获取任何类的内部信息(比如:成员变量,构造器,成员方法等)。并能操作对象的属性方法。在设计模式和框架地层都会涉及
- 加载完类之后,在堆中产生一个Class对象(一个类只有一个Class对象),对象包含类的完整结构信息。通过这个对象可以看到类的结构。Class对象就像一面镜子,通过镜子看到类的结构。
(2) 反射机制原理图
(3)反射可完成功能
- 运行时判断对象所属类
- 运行时构造一个任意类的对象
- 运行时得到一个类所具有的成员变量和方法
- 运行时调用任意一个对象的成员变量和方法
- 生产动态代理
(4) 反射相关类
1.java.lang.Class 代表一个类,Class对象表示某个类加载后在堆中的对象
2.java.lang.reflect.Method 代表类的方法,Method对象表示某个类的方法
3.java.lang.reflect.Field 代表类的成员变量,Filed对象表示某个类的成员变量
4.java.lang.reflect.Constructor 代表类的构造方法,Constructor对象表示构造器
举例:
public static void main(String[] args) throws IOException, ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException, NoSuchFieldException {
//1、使用Properties类读取配置信息
Properties prop = new Properties();
prop.load(new FileInputStream("src\\re.properties"));
String classfullpath = prop.getProperty("classfullpath");
String methodName = prop.getProperty("method");
//2、使用反射机制
//(1)加载类,返回class类型的对象cls
Class cls = Class.forName(classfullpath);
//(2)通过getInstance得到加载类的实例对象
Object o = cls.newInstance();
//(3)通过cls获得得到类的方法对象
Method method1 = cls.getMethod(methodName);
System.out.println("=====================");
//(4)调用方法 传统方法为对象.方法(), 反射机制 方法.invoke(对象)
method1.invoke(o);
//java.lang.reflect.Field 代表类的成员变量,Field对象表示某个类的成员变量
Field nameField = cls.getField("age"); //不能获取到私有的变量
System.out.println(nameField.get(o)); //传统写法对象.成员变量, 反射: 成员变量对象.get(对象)
//java.lang.constructor 代表类的构造方法,Constructor对象表示构造器
Constructor constructor = cls.getConstructor();
System.out.println(constructor);
Constructor constructor2 = cls.getConstructor(String.class);
System.out.println(constructor2);
}
(5)反射的优缺点
- 优点:可以动态的创建和使用对象(框架底层核心),使用灵活,没有反射机制,框架技术就失去了底层支撑
- 缺点:使用反射基本是解释执行,对执行速度有影响
- 改进:关闭访问检查
举例:
public static void main(String[] args) throws ClassNotFoundException, InvocationTargetException, NoSuchMethodException, IllegalAccessException, InstantiationException {
m1(); //传统方法
m2(); //反射方法
m3(); //反射优化
}
//传统方法调用hi
public static void m1(){
Cat cat = new Cat();
long start = System.currentTimeMillis();
for (int i = 0; i < 90000000; i++) {
cat.hi();
}
long end = System.currentTimeMillis();
System.out.println("传统方法调用hi耗时:"+(end-start));
}
//反射机制调用方法hi
public static void m2() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Class cls = Class.forName("JavaEE.chapter17.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
long start = System.currentTimeMillis();
for (int i = 0; i < 90000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("反射机制耗时:"+(end-start));
}
//反射机制优化
public static void m3() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, IllegalAccessException, InstantiationException {
Class cls = Class.forName("JavaEE.chapter17.Cat");
Object o = cls.newInstance();
Method hi = cls.getMethod("hi");
hi.setAccessible(true);//在反射调用方法时,取消访问检查
long start = System.currentTimeMillis();
for (int i = 0; i < 90000000; i++) {
hi.invoke(o);
}
long end = System.currentTimeMillis();
System.out.println("反射优化机制耗时:"+(end-start));
}
2、class类
(1)基本介绍
- class类也是类,因此继承了Object类
- Class类对象不是new出来的,而是系统创建出来的
- 对于某个类的class类对象,在内存中只有一份,因为类只加载一次
- 每个类的实例都会记住自己是由哪个Class实例所生成的
- 通过Class对象可以完整的得到一个类的完整结构
- class对象存在堆当中
- 类的字节码二进制数据,放在方法区当中,也称为类的元数据
举例:
public static void main(String[] args) throws ClassNotFoundException {
//1、class也是类,继承了Object类
//Class
//2、Class对象不是new出来的,而是系统创建的
//(1)传统new对象
/*
ClassLoader 类
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}*/
// Cat cat = new Cat();
//(2)反射方式
/*
ClassLoader 类, 仍然是通过ClassLoader 类加载Cat 类的Class 对象
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}*/
//3、对于某个类的class对象,在内存中只有一份,类只加载一次
Class cls1 = Class.forName("JavaEE.chapter17.Cat");
Class cls2 = Class.forName("JavaEE.chapter17.Cat");
System.out.println(cls1.hashCode());
System.out.println(cls2.hashCode());
}
(2)获取Class类对象的方式
-
Class.forName()
获取-
前提:已知一个类的全类名,并且该类在类路径下,可以通过Class的静态方法
forName
获取, -
应用场景:多用于配置文件,读取类的全路径,加载类
-
举例:
//1、加载类的情况下,使用getclass,多用于配置文件的时候 String classAllPath = "JavaEE.chapter17.Cat"; //通过读取文件配置获取 Class<?> cls1 = Class.forName(classAllPath); System.out.println(cls1);
-
-
类.Class()
获取-
前提:已经知道具体的类,通过类的Class进行获取,该方式最为安全可靠,程序性能最高
-
应用场景:多用于参数的传递,比如通过反射获取到对应的构造器对象
-
举例:
//2、类名.Class,加载类的情况下,用于参数传递 Class<?> cls2 = Cat.class; System.out.println(cls2);
-
-
对象.getClass()
获取-
前提:已经知道具体的对象实例
-
应用场景:有对象实例
-
举例:
//3、对象.getClass,运行的情况下,已经有对象实例,通过创建好的对象,获取Class对象 Cat cat = new Cat(); Class cls3 = cat.getClass(); System.out.println(cls3);
-
-
类加载
ClassLoader
的loadClass进行获取-
举例:
//4、通过类加载器【4种】获取类的Class对象 //(1)先得到类加载器 ClassLoader classLoader = cat.getClass().getClassLoader(); //(2)通过类加载器获取到Class对象 Class cls4 = classLoader.loadClass(classAllPath); System.out.println(cls4);
-
-
基本数据类型
.class
进行获取-
举例:
//5、基本数据类型(int,char,boolean,float,double,byte,long,short),可以通过.class方式获取到class类对象 Class<Integer> intClass = int.class; Class<Character> charClass = char.class; System.out.println(intClass); System.out.println(charClass);
-
-
基本数据类型的包装类
.TYPE
进行获取-
举例:
//6、基本数据类型的包装类,可以通过.TYPE得到Class对象 Class cls5 = Integer.TYPE; System.out.println(cls5);
-
(3)哪些类型有Class对象
- 外部类、成员内部类、静态内部类、局部内部类、匿名内部类
- interface:接口
- 数组
- enum:枚举
- annotation:注解
- 基本数据类型
- void
举例:
Class<String> cls1 = String.class;//外部类
Class<Serializable> cls2 = Serializable.class;//接口
Class<Integer[]> cls3 = Integer[].class;//数组
Class<float[][]> cls4 = float[][].class;//二维数组
Class<Deprecated> cls5 = Deprecated.class;//注解
//枚举
Class<Thread.State> cls6 = Thread.State.class;
Class<Long> cls7 = long.class;//基本数据类型
Class<Void> cls8 = void.class;//void数据类型
Class<Class> cls9 = Class.class;//class自己
3、类加载
(1)基本介绍
反射机制是Java实现动态语言的关键,也是通过反射实现类的动态加载
- 静态加载:**编译的时候加载相关的类,如果没有该类就会报错。**依赖性太强
- 动态加载:**运行的时候加载相关的类,如果运行的时候没有使用到这个类,即便不存在这个类,也不会报错。**降低了依赖性
(2)类加载的时机
- 当创建对象时(new),进行静态加载
- 当子类被加载时,父类也加载,进行静态加载
- 调用类的静态成员时,进行静态加载
- 通过反射时,进行动态加载
(3)类加载过程图
(4)类加载各阶段任务
-
加载阶段:
JVM
在该阶段主要目的是将字节码从不同的数据源(可能是class文件,也可以是jar包,或者是网络等等)转化为二进制字节流加载到内存中,并生成一个代表该类的java.lang.Class
对象
-
连接阶段:
-
验证 verification
- 目的是保证Class文件中的字节流包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全
- 包括:文件格式验证(是否以魔数
oxcafebabe
开头)、元数据验证、字节码验证和符号引用验证 - 可以考虑使用
-Xverify:none
,参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间
-
准备 preparation
JVM
在该阶段为静态变量,分配内存并默认初始化(对应的数据类型的默认初始化值,如0,0L,null,false等)。这些变量使用的内存将在方法区中进行分配
举例:
//类加载阶段中连接阶段,属性如何准备 //1、n1为实例属性,为静态变量,在准备阶段,不会分配内存 //2、n2为静态变量,分配内存n2是默认初始化为0,而不是20 //3、n3是static final是常量,与静态变量不一样,一旦赋值不会更改 n3 = 30 public int n1 = 10; public static int n2 = 20; public final static int n3 = 30;
-
解析 resolution
- 虚拟机将常量池内的符号引用替换为直接引用的过程
-
-
初始化阶段: Initialization
- 到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行()方法的过程
- ()方法是由编译器按照在源文件中出现的顺序,依次自动收集类中的所有的静态变量的赋值动作和静态代码块中的语句进行合并的
- 虚拟机会保证一个类的()方法在多线程环境中被正确的加锁、同步,如果多个线程同时初始化一个类,那么只有一个线程回去执行这个类的()方法,其他线程都需要阻塞等待,直到活动线程执行()完毕。
举例:
public class ClassLoad03 { public static void main(String[] args) { System.out.println(B.num); //直接使用类的静态属性,也会执行类加载过程 /* * 1、加载B类,并生产B的class对象 * 2、连接 num = 0 * 初始化阶段 * 依次收集类中的所有静态变量的赋值动作和静态代码块中的语句,并对其进行合并 * clinit(){ System.out.println("B 静态代码块被执行"); num = 300; //被后面的语句合并,这里不起作用,最后输出100 num = 100; } * */ new B();//加断点,追溯源码看是否有同步机制 /* * protected Class<?> loadClass(String name, boolean resolve) *throws ClassNotFoundException{ //正因为有这个机制,才能保证某个类在内存中, 只有一份Class 对象 synchronized (getClassLoadingLock(name)) { //.... } } * * * */ } } class B{ static { System.out.println("B 静态代码块被执行"); num = 300; } static int num = 100; // public B(){ // System.out.println("B 的构造器被执行"); // } }
4、反射获取类的结构信息
(1)java.lang.Class 类
getName:获取全类名
getSimpleNmae:获取简单类名
getFields:获取所有public修饰的属性,包含本类以及父类的
getDeclaredFields:获取本类中的所有属性
getMethods:获取所有public修饰的方法,包含本类以及父类的
getDeclaredMethods:获取本类的所有方法
getConstructors:获取本类所有public修饰的构造器
getDeclaredConstructors:获取本类的所有构造器
getPackage:以Package形式返回包信息
getSuperClass:以Class形式返回父类信息
getInterfaces:以Class[]返回接口信息
getAnnotations:以Annotation[]形式返回注解信息
(2)java.lang.reflect.Field 类
getModifiers:以int形式返回修饰符
【默认修饰符伟0,public为1,private为2,protected为4,static为8,final为16】
getType:以Class形式返回类型
getName:返回属性名
(3)java.lang.reflect.Method 类
getModifiers:以int形式返回修饰符
【默认修饰符伟0,public为1,private为2,protected为4,static为8,final为16】
getReturnType:以Class形式获取返回类型
getName:返回方法名
getParameterTypes:以Class[]类型返回参数类型数组
(4)java.lang.reflect.Constructor 类
getModifiers:以int形式返回修饰符
【默认修饰符伟0,public为1,private为2,protected为4,static为8,final为16】
getName:返回构造器名
getParameterTypes:以Class[]类型返回参数类型数组
举例:
public class ReflectionUtils {
public static void main(String[] args) {
}
//第一组API
@Test
public void api_01() throws ClassNotFoundException {
//得到Class对象
Class<?> personCls = Class.forName("JavaEE.chapter17.Person");
//getName:获取全类名
System.out.println(personCls.getName());
//getSimpleNmae:获取简单类名
System.out.println(personCls.getSimpleName());
//getFields:获取所有public修饰的属性,包含本类以及父类的
Field[] fields = personCls.getFields();
for (Field field : fields) {
System.out.println("本类与父类的属性"+field.getName());
}
//getDeclaredFields:获取本类中的所有属性
Field[] declaredields = personCls.getDeclaredFields();
for (Field field : declaredields) {
System.out.println("本类的所有属性"+field.getName());
}
//getMethods:获取所有public修饰的方法,包含本类以及父类的
Method[] methods = personCls.getMethods();
for (Method method : methods) {
System.out.println("本类与父类的方法"+method.getName());
}
//getDeclaredMethods:获取本类的所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method method : declaredMethods) {
System.out.println("本类的方法"+method.getName());
}
//getConstructors:获取本类所有public修饰的构造器
Constructor<?>[] constructors = personCls.getConstructors();
for (Constructor<?> constructor : constructors) {
System.out.println("本类所有public构造器"+constructor);
}
//getDeclaredConstructors:获取本类的所有构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> constructor : declaredConstructors) {
System.out.println("本类的所有构造器"+constructor);
}
//getPackage:以Package形式返回包信息
System.out.println(personCls.getPackage());
//getSuperClass:以Class形式返回父类信息
Class<?> superclass = personCls.getSuperclass();
System.out.println("父类的class对象"+superclass);
//getInterfaces:以Class[]返回接口信息
Class<?>[] interfaces = personCls.getInterfaces();
for (Class<?> anInterface : interfaces) {
System.out.println("接口信息"+anInterface);
}
//getAnnotations:以Annotation[]形式返回注解信息
Annotation[] annotations = personCls.getAnnotations();
for (Annotation annotation : annotations) {
System.out.println("注解信息="+annotation);
}
}
@Test
//第二组API
public void API_02() throws ClassNotFoundException {
//得到Class对象
Class<?>personCls = Class.forName("JavaEE.chapter17.Person");
//getDeclaredFields:获取本类中所有属性
//规定说明: 默认修饰符是0 , public 是1 ,private 是2 ,protected 是4 , static 是8 ,final 是16
Field[] declaredFields = personCls.getDeclaredFields();
for (Field declaredField : declaredFields) {
System.out.println("本类中所有属性=" + declaredField.getName()
+ " 该属性的修饰符值=" + declaredField.getModifiers()
+ " 该属性的类型=" + declaredField.getType());
}
//getDeclaredMethods:获取本类中所有方法
Method[] declaredMethods = personCls.getDeclaredMethods();
for (Method declaredMethod : declaredMethods) {
System.out.println("本类中所有方法=" + declaredMethod.getName()
+ " 该方法的访问修饰符值=" + declaredMethod.getModifiers()
+ " 该方法返回类型" + declaredMethod.getReturnType());
//输出当前这个方法的形参数组情况
Class<?>[] parameterTypes = declaredMethod.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该方法的形参类型=" + parameterType);
}
}
//getDeclaredConstructors:获取本类中所有构造器
Constructor<?>[] declaredConstructors = personCls.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
System.out.println("====================");
System.out.println("本类中所有构造器=" + declaredConstructor.getName());//只是输出名
Class<?>[] parameterTypes = declaredConstructor.getParameterTypes();
for (Class<?> parameterType : parameterTypes) {
System.out.println("该构造器的形参类型=" + parameterType);
}
}
}
}
class A{
public String hobby;
public A(){
}
public void hi(){
}
}
interface IA{
}
interface IB{
}
@Deprecated
class Person extends A implements IA,IB{
//四种不同类型修饰符的属性
public String name;
protected int age;
String job;
private double sal;
public Person(){}
private Person(String name){}
public Person(String name, int age, String job, double sal) {}
//四种不同类型修饰符的方法
public void m1() {
}
protected void m2(){
}
void m3(){
}
private void m4(){
}
5、反射创建对象
- 方式一:调用类中的public修饰的无参构造器
- 方式二:调用类中的指定构造器
- Class类的相关方法
newInstance
:调用类中的午餐构造器,获取对应的类的信息getConstructor(class...class)
:根据参数列表获取对应的public
构造器对象getDeclaredConstructor(class....class)
:根据参数列表,获取对应的所有构造器对象
- Constructor类的相关方法
setAccessible
:爆破newInstance(object..obj)
:调用构造器
举例:
public class ReflectCreateInstance {
public static void main(String[] args) throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
//1、先得到Class对象
Class<?> userCls = Class.forName("JavaEE.chapter17.User");
//2、通过public的无参构造器
Object o = userCls.getDeclaredConstructor().newInstance();
System.out.println(o);
//3、通过public的有参构造器
//3.1先得到构造器
Constructor<?> constructor = userCls.getConstructor(String.class);
//3.2然后用构造器创建实例
Object user1 = constructor.newInstance("user1");
System.out.println(user1);
//4、通过非公有的构造器创建实例
//4.1先得到构造器
Constructor<?> constructor2 = userCls.getDeclaredConstructor(int.class,String.class);
//私有构造器,得先爆破,才能使用,暴破【暴力破解】, 使用反射可以访问private 构造器/方法/属性, 反射面前,都是纸老虎
constructor2.setAccessible(true);
//4.3然后用构造器创建实例
Object user2 = constructor2.newInstance(200,"user2");
System.out.println(user2);
}
}
class User { //User 类
private int age = 10;
private String name = "名字";
public User() {//无参public
}
public User(String name) {//public 的有参构造器
this.name = name;
}
private User(int age, String name) {//private 有参构造器
this.age = age;
this.name = name;
}
public String toString() {
return "User [age=" + age + ", name=" + name + "]";
}
}
6、反射访问类中的成员
(1)访问属性
-
根据属性名获取Field对象
Field f = clazz对象.getDeclaredField(属性名);
-
爆破
f.setAccessible(true);
-
访问
f.set(o,值) //o表示对象 syso(f.get(o)) //o表示对象
-
如果是静态属性,则set和get中的参数o,可以写成null
举例:
public class ReflectAccessProprty {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchFieldException {
//1. 得到Student 类对应的Class 对象
Class<?> stuClass = Class.forName("JavaEE.chapter17.Student");
//2. 创建对象
Object o = stuClass.newInstance();//o 的运行类型就是Student
System.out.println(o.getClass());//Student
//3. 使用反射得到age 属性对象
Field age = stuClass.getField("age");
age.set(o,88); //反射操作属性
System.out.println(age.get(o));
//4. 使用反射操作name 属性
Field name = stuClass.getDeclaredField("name");
//对name 进行暴破, 可以操作private 属性
name.setAccessible(true);
name.set(o, "小明");
System.out.println(name.get(o));
name.set(null,"小红");//因为name 是static 属性,因此o 也可以写出null
System.out.println(name.get(o));
System.out.println(name.get(null));//获取属性值, 要求name 是static
}
}
class Student {//类
public int age;
private static String name;
public Student() {//构造器
}
public String toString() {
return "Student [age=" + age + ", name=" + name + "]";
}
}
(2)访问方法
-
根据方法名和参数列表获取Method方法
Method m = clazz.getDeclaredMethod(方法名,XX.class)//得到本类的所有方法
-
获取对象
Object o = clazz.newInstance();
-
爆破
m.setAccessible(true);
-
访问
Object returnValue = m.invoke(o,实参列表);
-
注意:如果是静态放哪发,则invoke的参数o,可以写成null
举例:
public class ReflecAccessMethod {
public static void main(String[] args) throws ClassNotFoundException, InstantiationException, IllegalAccessException, NoSuchMethodException, InvocationTargetException {
//1. 得到Boss 类对应的Class 对象
Class<?> bossCls = Class.forName("JavaEE.chapter17.Boss");
//2. 创建对象
Object o = bossCls.newInstance();
//3. 调用public 的hi 方法
//Method hi = bossCls.getMethod("hi", String.class);
//3.1 得到hi 方法对象
Method hi = bossCls.getDeclaredMethod("hi", String.class);
//3.2调用方法
hi.invoke(o,"大怪物");
//4. 调用private static 方法
//4.1 得到say 方法对象
Method say = bossCls.getDeclaredMethod("say", int.class, String.class, char.class);
//4.2 因为say 方法是private, 所以需要暴破,原理和前面讲的构造器和属性一样
say.setAccessible(true);
System.out.println(say.invoke(o, 100, "张三", '男'));
//4.3 因为say 方法是static 的,还可以这样调用,可以传入null
System.out.println(say.invoke(null, 200, "李四", '女'));
//5. 在反射中,如果方法有返回值,统一返回Object , 但是他运行类型和方法定义的返回类型一致
Object reVal = say.invoke(null, 300, "王五", '男');
System.out.println("reVal 的运行类型=" + reVal.getClass());//String
System.out.println(reVal);
//在演示一个返回的案例
Method m1 = bossCls.getDeclaredMethod("m1");
Object reVal2 = m1.invoke(o);
System.out.println("reVal2 的运行类型=" + reVal2.getClass());//Monster
}
}
class Monster {}
class Boss {//类
public int age;
private static String name;
public Boss() {//构造器
}
public Monster m1() {
return new Monster();
}
private static String say(int n, String s, char c) {//静态方法
return n + " " + s + " " + c;
}
public void hi(String s) {//普通public 方法
System.out.println("hi " + s);
}
}
, 但是他运行类型和方法定义的返回类型一致
Object reVal = say.invoke(null, 300, “王五”, ‘男’);
System.out.println(“reVal 的运行类型=” + reVal.getClass());//String
System.out.println(reVal);
//在演示一个返回的案例
Method m1 = bossCls.getDeclaredMethod("m1");
Object reVal2 = m1.invoke(o);
System.out.println("reVal2 的运行类型=" + reVal2.getClass());//Monster
}
}
class Monster {}
class Boss {//类
public int age;
private static String name;
public Boss() {//构造器
}
public Monster m1() {
return new Monster();
}
private static String say(int n, String s, char c) {//静态方法
return n + " " + s + " " + c;
}
public void hi(String s) {//普通public 方法
System.out.println("hi " + s);
}
}