Java反射

246 阅读8分钟

反射基本概念

反射机制的概念

JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;

对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。

加载完类之后,在堆中就产生了一个Class类型的对象(一个类只有一个Class对象) ,这个对象包含了类的完整结构信息。

通过这个对象得到类的结构。这个Class对象就像一面镜子, 透过这个镜子看到类的结构,所以,形象的称之为:反射

反射机制原理图

image.png 反射的功能

  • 在运行时判断任意一个对象所属的类
  • 在运行时构造任意一个类的对象
  • 在运行时得到任意一个类所具有的成员变量和方法
  • 在运行时调用任意一个对象的成员变量和方法
  • 生成动态代理

反射相关的主要类

  • java.lang.Class:代表一个类,Class对象表示某 个类加载后在堆中的对象
  • java.lang.reflect.Method:代表类的方法,Method对象表示某个类的方法
  • java.lang.reflect.Field:代表类的成员变量,Field对象表示某个类的成员变量
  • java.lang.reflect.Constructor:·代表类的构造方法,Constructor对象表示

这些类都在java.lang.reflection中

反射的优缺点

优点:可以动态的创建和使用对象(也是框架底层核心),使用灵活,没有反射机制,框架技术就失去底层支撑。

缺点:使用反射基本都是解释执行,对执行速度有影响

反射调用优化(关闭访问检测)

  • Method和Field、 Constructor对象都有setAccessible(方法
  • setAccessible作用是启动和禁用访问安全检查的开关
  • 参数值为true表示反射的对象在使用时取消访问检查,提高反射的效率。参数值为false则表示反射的对象执行访问检查

Class对象

Class类的类图

image.png

Class基本信息

  • Class也是类,因此也继承0bject类,Class 类的实例对象表示正在运行的 Java 应用程序中的类和接口
  • Class 类没有公共构造方法,Class类对象不是new出来的,而是在加载类的时候由JVM自动创建的
  • 对于某个类的Class类对象,在内存中只有一份,因为类只加载一次
  • 每个类的实例都会记得自己是由哪个Class实例所生成
  • 通过Class对象可以完整地得到一个类的完整结构(一系列API)
  • 类的字节码二进制数据(也称元数据,包括方法代码,变量名,方法名,访问权限)是存放到方法区的
 public class Ref {
     public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
         Class cls1 = Class.forName("com.JavaBasic.Person");
         Class cls2 = Class.forName("com.JavaBasic.Person");
         
         // Class类对象,在内存中只存在一份,故其hashCode值相同
         System.out.println(cls1.hashCode());
         System.out.println(cls2.hashCode());
     }
 }

获取Class类对象的方式

  • 根据一个类的全类名,并且该类在这其类路径下,可以通过Class类的静态方法 forName("全类名")获取Class类对象,可能会抛出

    ClassNotFoundException异常。应用场景:多用于配置文件,读取类全路径,加载类

  • 前提:若已知具体的类,通过类的class获取,该方式最为安全可靠,程序性能最高,比如Class cls2 = Cat.class;应用场景:多用于参数传递,比如通过反射得到对应构造器对象

  • 前提:已知某个类的实例,调用该实例的getClass()方法获取Class对象,比如Class clazz =对象.getClass();应用场景:通过创建好的对象,获取Class对象

  • 通过类加载器获取Class类对象:ClassLoader cl =对象.getClass().getClassLoader()Class clazz4 = cl.loadClass( "类的全类名")

  • 基本数据类型(int,char,long等)可以通过 Class cls = 基本数据类型.class

  • 基本数据类型对应的包装类,可以通过.TYPE得到Class类对象,Class cls = 包装类.TYPE

 package com.JavaBasic;
 ​
 /**
  * @author hjw
  * @date 2021/10/16 16:19
  */
 public class Ref {
     public static void main(String[] args) throws ClassNotFoundException, IllegalAccessException, InstantiationException {
         // Class.forName的方式获取Class对象
         String classPath = "com.JavaBasic.Person";
         Class cls = Class.forName(classPath);
         System.out.println(cls);
 ​
         // 类名.class 获取Class对象
         Class cls2 = Person.class;
         System.out.println(cls2);
 ​
         // 对象.getClass 获取Class对象
         Person person = new Person();
         Class cls3 = person.getClass();
         System.out.println(cls3);
 ​
         // 通过类加载器获得Class类对象
         // 先获得类加载器
         ClassLoader classLoader = person.getClass().getClassLoader();
         // 再获得Class对象
         Class cls4 = classLoader.loadClass(classPath);
         System.out.println(cls4);
         
         // 基本数据获取其对应的Class类对象
         Class<Integer> integerClass = int.class;
         Class<Double> doubleClass = double.class;
         Class<Character> characterClass = char.class;
         
         // 包装类获取其对应的Class类对象
         Class<Integer> type1 = Integer.TYPE;
         Class<Character> type2 = Character.TYPE;
 ​
     }
 }
 ​

哪些类型有Class对象

  • 外部类,成员内部类,静态内部类,局部内部类,匿名内部类
  • interface :接口
  • 数组
  • enum :枚举
  • annotation :注解
  • 基本数据类型
  • void
 package com.JavaBasic;
 ​
 import java.io.Serializable;
 ​
 /**
  * @author hjw
  * @date 2021/10/16 20:43
  */
 public class Ref2 {
     public static void main(String[] args) {
         // 外部类
         Class<String> cls1 = String.class;
         
         // 接口
         Class<Serializable> cls2 = Serializable.class;
         
         // 数组
         Class<Integer[]> cls3 = Integer[].class;
         
         // 二维数组
         Class<Integer[][]> cls4 = Integer[][].class;
         
         // 注解
         Class<Deprecated> cls5 = Deprecated.class;
         
         // void数据类型
         Class<Void> cls6 = void.class;
         
         // Class
         Class<Class> cls7 = Class.class;
     }
 }

类加载

类加载

反射机制是java实现动态语言的关键,也就是通过反射实现类动态加载。

  • 静态加载:编译时加载相关的类,如果没有则报错,依赖性太强
  • 动态加载:运行时加载需要的类,如果运行时不用该类,即使不存在该类,也不报错,降低了依赖性

类加载时机

  • 静态加载:

    • 当创建对象时(new)
    • 当子类被加载时,父类也加载
    • 调用类中的静态成员
  • 动态加载:

    • 通过反射 Class.forName("com.test.Cat");

类加载过程图

  • 编写完一个Java文件之后,java文件都会被编译成一个.class文件(字节码文件)

  • class文件在程序运行时会被ClassLoader加载到JVM中,当一个类被加载以后,JVM就会在内存中自动产生一个Class对象(唯一)

image.png 类加载各阶段完成的任务

image.png 加载阶段

image.png 验证阶段

  • 目的是为了确保Class文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全。
  • 验证内容包括:文件格式验证(是否以魔数oxcafebabe开头)、元数据验证、字节码验证和符号引用验证
  • 可以考虑使用-Xverify:none参数来关闭大部分的类验证措施,缩短虚拟机类加载的时间。

准备阶段

JVM会在该阶段对静态变量,分配内存并默认初始化(对应数据类型的默认初始值,如0、0L、 null、 false 等)。这些变量所使用的内存都将在方法区中进行分配

解析阶段

虚拟机将常量池中的符号引用替换为直接引用的过程

Initialization 初始化

  • 到初始化阶段,才真正开始执行类中定义的Java程序代码,此阶段是执行<clinit>()方法的过程。
  • <clinit>()方法是由编译器按语句在源文件中出现的顺序,依次自动收集类中的所有静态变量的赋值动作和静态代码块中的语句,并进行合并。
  • 虚拟机会保证一个类的<clinit>()方法在多线程环境中被正确地加锁、同步,如果多个线程同时去初始化一个类, 那么只会有一个线程去执行这个类的<clinit>()方法,其他线程都需要阻塞等待,直到活动线程执行<clinit>方法完毕

反射的使用

类的结构信息

Class类

  • getName:获取全类名
  • getSimpleName:获取简单类名
  • getFields:获取所有public修饰的属性,包含本类以及父类的
  • getDeclaredFields:获取本类中所有属性
  • getMethods:获取所有public修饰的方法,包含本类以及父类的
  • getDeclaredMethods:获取本类中所有方法
  • getConstructors:获取本类所有public修饰的构造器
  • getDeclaredConstructors:获取本类中所有构造器
  • getPackage:以Package形式返回包信息
  • getSuperClass:以Class形式返回父类信息
  • getInterfaces:以Class[]形式返回接口信息
  • getAnnotations:以Annotation[形式返回注解信息

Field类

  • getModifiers:以int形式返回修饰符;【默认修饰符是0,public 是1,private 是2,protected 是4,static是8 ,final是16,public(1) + static (8) = 9】
  • getType:以Class形式返回类型
  • getName:返回属性名

Method类

  • getModifiers:以int形式返回修饰符【默认修饰符是0,public 是1, private是2, protected是4,static是8, final 是16】
  • getReturnType:以Class形式获取返回类型
  • getName:返回方法名
  • getParameter Types:以Class[]返回参数类型数组

Constructor类

  • getModifiers:以int形式返回修饰符
  • getName:返回构造器名(全类名)
  • getParameter Types:以Class[]返回参数类型数组

通过反射创建对象

通过反射创建对象的方式

  • 调用类中的public修饰的无参构造器
  • 调用类中的指定构造器

通过反射访问类中的成员

访问属性

  • 根据属性名获取Field对象Field f = clazz对象.getDeclaredField(属性名);

  • 暴破(获取private属性): f.setAccessible(true); //f是Field

  • 访问

    • f.set(o,值); //。表示对象
    • syso(f.get())://o表示对象
  • 注意:如果是静态属性,则set和get中的参数o,可以写成null