第23章 反射

韩顺平_循序渐进学Java零基础_第23章 反射(P711 - P730)

第23章 反射

711. 反射机制问题

  • 设计模式的 OCP(开闭) 原则:通过外部文件配置,在不修改源码的情况下,来控制程序;所谓开指功能开放,所谓闭指源码封闭

712. 反射快速入门

// 加载配置文件,读取配置文件信息
Properties properties = new Properties();
properties.load(new FileInputStream("reflection.properties"));
String classPath = properties.getProperty("classPath");
String methodName = properties.getProperty("method");

// 加载类信息
Class<?> aClass = Class.forName(classPath);
// 获得类的一个实例
Object o = aClass.newInstance();
// 获得类中方法实例
Method method = aClass.getMethod(methodName);
// 调用方法
method.invoke(o);

713. 反射原理图

  • 反射机制允许程序在执行期借助于 Reflection API 取得任何类的内部信息(如成员变量、构造器、成员方法等),并能操作对象的属性及方法,反射在设计模式和框架底层广泛使用

  • 加载完类之后,在堆中就产生了一个 Class 类型(Class 本身也是一个类)的对象(一个类只有一个 Class 对象,因为类只加载一次),这个对象包含了类的完整结构信息,通过这个对象就可以得到类的所有信息

  • Java 程序的三个阶段:

    1. 代码阶段(编译阶段):将源码 .java 文件通过 javac 编译得到 .class 字节码文件
    2. Class 类阶段(加载阶段):通过类加载器(ClassLoader)将 .class 字节码文件加载到堆中生成 Class 类的对象,该对象记录了 .class 中类的字段 Field[] fields、构造器 Constructor[] constructors、方法 Method[] methods 等信息
    3. Runtime(运行阶段):通过 new 创建出的对象知道自己与哪个 Class 类型的对象关联
  • 综上分析,通过 new 出来的对象也可以拿到它所关联的 Class 类型的对象;也可以直接拿到某个类的 Class 类型的对象进而操纵该类,比如创建该类的对象、调用该类的方法等等

Java 程序在计算机中的三个阶段

714. 反射相关类

  • 反射机制的作用:
    1. 在运行时判断任意一个对象所属的类
    2. 在运行时构造任意一个类的对象
    3. 在运行时得到任意一个类所具有的字段和方法
    4. 在运行时调用任意一个类所具有的字段和方法
    5. 生成动态代理
  • java.lang.Class 代表一个类,Class 对象代表某个类的字节码文件被类加载器加载后在堆中生成的 Class 类型的对象
// 得到类的公有字段
Field ageField = aClass.getField("age");
// 获取字段的值
System.out.println(ageField.get(o));

// 得到类的公有构造器
Constructor<?> constructor = aClass.getConstructor(String.class, int.class);
// 调用构造器创建对象
constructor.newInstance("大黄", 12);

715. 反射调用优化

  • 反射优点:可以动态创建和使用对象(框架底层核心),使用灵活,没有反射机制框架就失去了灵魂
  • 反射缺点:使用反射代码基本是解释执行,对执行速度有一定影响
  • 反射调用优化:关闭访问检查;Method、Field、Constructor 类都有一个 setAccessible(boolean) 方法,作用是启动和禁用访问安全检查的开关;传入参数 true 表示反射的对象在使用时取消安全检查,提高反射效率,提高效果并不明显

716. Class类分析

  • Class 类本身也是类,继承自 Object 类
  • Class 类对象不是 new 出来的,而是系统创建的,通过 ClassLoader 类的 loadClass 方法创建
  • 对于某个类的 Class 对象,在堆中有且仅有一份,因为类只加载一次
  • 每个类的实例(new 创建)都明确知道自己是与哪一个 Class 对象关联:obj.getClass()
  • 通过一个类对应的 Class 对象,可以获取到该类的完整结构
  • 加载类在堆里生成 Class 对象的同时也会在方法区生成对应类的字节码的二进制数据,也称为类的元数据,该二进制数据引用到 Class 类型对象

717. Class常用方法

方法 功能
static Class forName(String) 获得指定类名的 Class 对象
Object newInstance() 调用缺省构造函数,获得 Class 对象的一个实例
getName() 获得 Class 对象所表示的实体名称
Class getSuperClass() 获得当前 Class 对象的父类的 Class 对象
Class[] getInterfaces 获得 Class 对象的接口
ClassLoder getClassLoder() 获得类的加载器
Constructor[] getConstructors() 获得非私有构造器对象
Field[] getDeclaredFields() 获得所有字段对象
Method getMethod() 获得一个非私有 Method 对象
String classPath = "com.springbear.Car";

Class<?> clazz = Class.forName(classPath);
// 输出此 Class 对象是哪个类的 Class 对象
System.out.println(clazz);
// 获得运行类型
System.out.println(clazz.getClass());
// 获得包名
System.out.println(clazz.getPackage().getName());
// 获得全类名
System.out.println(clazz.getName());
// 获得实例
Object obj = clazz.newInstance();
System.out.println(obj);
// 获取公有属性
Field bland = clazz.getField("bland");
System.out.println(bland.get(obj));
// 给属性设置值
bland.set(obj, "宝马");
System.out.println(bland.get(obj));

718. 获取Class对象六种方式

  • 编译阶段:Class.forName(String);多用于配置文件,读取类全路径,加载类
  • 类加载阶段:类.class;多用于参数传递,此方式最为安全可靠,程序性能最高
  • 运行阶段:对象.getClass();通过创建好的类的对象来获取 Class 对象也即运行类型
  • 类加载器:
Car car = new Car();
// 获得类对应的类加载器
ClassLoader classLoder = car.getClass().getClassLoader();
// 获得 Class 对象
Class<?> aClass = classLoder.loadClass("com.springbear.Car");
  • 八大基本数据类型(byte、boolean、char、short、int、float、long、double)可直接使用:Class cls = int.class
  • 基本数据类型对应的包装类,可以通过 .TYPE获得 Class 对象:Class cls = Integer.TYPE

719. 哪些类型有Class对象

  • 外部类、内部类、接口、数组、枚举、注解、基本数据类型、void

720. 动态和静态加载

  • 反射机制是 Java 实现动态语言的关键,也就是通过反射实现类动态加载
  • 静态加载:编译时加载相关的类,如果没有相关类则报错,依赖性很强
  • 动态加载:只在运行时加载需要的类,如果运行时未使用到该类,则不报错,降低了依赖性
  • 类加载的时机:
    1. new 创建对象时
    2. 其子类被加载时,父类也被加载
    3. 访问到类的静态成员时
    4. 通过反射加载

721. 类加载流程图

  • .java 文件通过 javac 编译得到 .class 文件,通过 java 运行 .class 文件时发生类的加载,类加载分为三个阶段:加载(loading)-> 连接(linking)-> 初始化(initialization)
  • 加载(loading):将类的 .class 文件读入内存,并为之创建一个 Class 类型的对象,由类加载器完成
  • 连接(linking):将类的二进制数据合并到 JRE 中。连接(linking)又分为验证(verification)、准备(preparation)、解析(resolution)三个子阶段
    1. 验证(verification):对文件的安全性进行校验,比如说文件格式是否正确、元数据验证是否正确、字节码是否正确、符号引用是否正确
    2. 准备(preparation):静态变量分配内存并完成默认初始化
    3. 解析(resolution):JVM 将常量池中的符号引用替换成直接引用
  • 初始化(initialization):JVM 负责对类进行初始化(显示初始化),主要指静态成员
  • 类加载后内存布局情况:在方法区存放类的字节码的二进制数据(元数据);在堆中存放类的 Class 对象(数据结构);且方法区的二进制数据引用到对应的 Class 对象

类加载的三个阶段

722. 类加载五个阶段1

  • 加载阶段:将字节码从不同的数据源(可能是 .class 文件、也可能是 jar 包、甚至是网络)转化为二进制字节流加载到内存(方法区)中,并生成一个代表该类的 java.lang.Class 对象
  • 连接之验证阶段:目的是为了确保 .class 文件的字节流中包含的信息符合当前虚拟机的要求,并且不会危害虚拟机自身的安全;验证包括对文件格式验证(是否以魔数 oxcafebabe 开头)、元数据验证、字节码验证和符号引用验证;可以考虑使用 -Xverify:none 参数来关闭大部分的类验证措施,缩短虚拟机加载时间
  • 连接之准备阶段:JVM 会在此阶段给静态变量分配内存并执行默认初始化,这些变量所使用的内存都会在方法区中进行分配
// 在连接的准备子阶段先默认初始化为 0,后在初始化阶段再显式初始化为 1
public static int num1 = 1;
// 在连接的准备子阶段直接初始化为 2
public static final int num2 = 2;
  • 连接之解析阶段:JVM 将常量池内的符号引用替换为直接引用的过程

723. 类加载五个阶段2

  • 初始化阶段:真正执行类中定义的 Java 代码,此阶段是执行 () 方法的过程
  • () 方法是由编译器按语句在源文件中的出现顺序,依次自动收集类中所有的静态变量的赋值动作和静态代码块中的语句,并进行合并
static {
    
    
    System.out.println("静态代码块被执行!");
    num = 300;
}
public static int num = 100;
// 经 <clinit>() 方法收集合并后如下
System.out.println("静态代码块被执行!");
num = 100;
  • JVM 保证一个类的 () 方法在多线程环境中会被正确地加锁、同步,如果多个线程同时去初始化一个类,那么只有一个线程会去执行 () 方法,其它线程都需要阻塞等待,直到活动线程执行完 () 方法

724. 获取类结构信息1

方法名 功能
getName 获取全类名
getSimpleName 获取简单类名
getFields 获取所有 public 修饰的字段,本类以及父类
getDeclaredFidles 获取本类中的所有字段
getMethods 获取所有 public 修饰的方法,本类以及父类
getDeclaredMethods 获取本类中的所有方法
getConstructors 获取所有 public 修饰的构造器,只包含本类
getDeclaredConstructors 获取本类中的所有构造器
getPackage 以 Package 形式返回包信息
getSuperClass 以 Class 形式返回父类信息
getAnnotations 以 Annotation[] 形式返回注解信息

725. 获取类结构信息2

类名 方法名 功能
java.lang.reflect.Field getModifiers() 以 int 形式返回修饰符:默认修饰符是 0,public 是 1,private 是 2,protected 是 4,static 是 8,final 是 16;多重修饰符则相加
getType() 以 Class 形式返回类型
getName() 返回字段名
java.lang.reflect.Method getModifiers 以 int 形式返回修饰符
getReturnType 以 Class 类型获取方法返回类型
getName 返回方法名
getParameterTypes 以 Class[] 形式返回参数类型数组
java.lang.reflect.Constructor getModifiers 以 int 形式返回修饰符
getName 返回构造器的全类名
getParameterTypes 以 Class[] 形式返回参数类型数组

726. 反射爆破创建实例

  • 通过反射创建对象方式一:调用类中的 public 修饰的无参构造器
Class<?> tempClass = Class.forName("com.springbear.Temp");
Object o = tempClass.newInstance();
  • 通过反射创建对象方式一:调用类中指定的构造器
Class<?> userClass = Class.forName("com.springbear.User");
// 获得指定构造器
Constructor<?> userConstructor = userClass.getDeclaredConstructor(int.class, String.class);
// 爆破
userConstructor.setAccessible(true);
// 调用构造器
Object mary = userConstructor.newInstance(10, "mary");
System.out.println(mary);

727. 反射爆破操作属性

Class<?> userClass = Class.forName("com.springbear.User");
Object userObject = userClass.newInstance();

// 获得指定字段
Field name = userClass.getDeclaredField("name");
// 爆破
name.setAccessible(true);
// 获得值
System.out.println(name.get(userObject));
// 设置值
name.set(userObject, "Spring-_-Bear");
System.out.println(name.get(userObject));
  • 如果获取的是静态属性,则 set 和 get 方法中传入的对象参数可以是 null

728. 反射爆破操作方法

Class<?> userClass = Class.forName("com.springbear.User");
Object userObject = userClass.newInstance();

// 获取指定的方法
Method loginMethod = userClass.getDeclaredMethod("login", String.class, String.class);
// 爆破
loginMethod.setAccessible(true);
// 调用方法
Object springbear = loginMethod.invoke(userObject, "springbear", "123");
System.out.println(springbear);
  • 如果是静态方法,则 invoke 的对象参数可以写成 null

729. 反射课后练习

  • 利用反射机制在指定目录下创建文件
// 加载类信息
Class<?> fileClass = Class.forName("java.io.File");
// 获取所有构造器
Constructor<?>[] declaredConstructors = fileClass.getDeclaredConstructors();
for (Constructor<?> declaredConstructor : declaredConstructors) {
    
    
    declaredConstructor.setAccessible(true);
    System.out.println(declaredConstructor);
}
// 获得指定的构造器
Constructor<?> constructor = fileClass.getConstructor(String.class);
Object file = constructor.newInstance("d:\\aa.txt");
// 获得指定方法
Method createNewFile = fileClass.getMethod("createNewFile");
// 调用方法
createNewFile.invoke(file);

730. 反射梳理

猜你喜欢

转载自blog.csdn.net/weixin_51008866/article/details/121482964