• Java> Java核心卷读书笔记


    反射简介

    能够分析类能力的程序成为反射(reflective)。

    反射可以用来干什么?
    反射经常用于构建库或工具,主要包含以下能力:

    • 运行时分析类的能力;
    • 运行时查看对象,如编写一个toString查看对象属性;
    • 实现通用的数组操作代码;
    • 利用Method对象,类似于C/C++函数指针;

    Class类

    主要作用:维护Java对象类型信息 -- 程序运行时,Java运行时系统始终为所有对象维护一个被成为运行时的类型标识。该信息跟踪者每个对象所属的类。虚拟机利用运行时类型信息选择相应方法执行。
    保存这些维护类型信息的类称为Class。

    获取Class类对象

    如何获取Class类对象?
    有3种方法:

    1. 对象的getClass()方法;
    2. Class静态方法Class.forName();
    3. 类名.class;

    示例:

    // 方法1: 一个类的对象 -> Class类对象
    Employee e;
    Class c = e.getClass(); // 通过getClass(), 获取对象e的类型信息
    
    // 方法2: 类名称: -> Class 类对象
    String className = "java.util.Random"; // 类名(需要包含包的路径)
    Class c2 = Class.forName(className) ;  // 通过Clas静态方法forName(),
    
    // 方法3: 类名.class -> Class类对象
    Class random = Random.class;
    Class cint = int.class;
    Class cdoubles = Double[].class;
    

    Class类包含哪些类的信息?

    1. 类名;
    2. 创建类的实例;
    3. 比较类型信息;

    读取类名

    Class的getName方法可以读取类名,包也作为类名的一部分

    System.out.println(e.getClass.getName() + " " + e.getName()); 
    

    创建类的实例

    Class对象的newInstance方法

    // 根据不同获取Class对象方式, 分为三种方式, 不过都是调用Class对象的newInstance方法
    // 方式一
    Employee e = new Employee();
    e.getClass().newInstance();
    
    // 方式二
    String s = "java.util.Random";
    Object m = Class.forName(s).newInstance();
    
    // 方式三
    Object m = Integer.class.newInstance();
    

    比较类型信息"=="

    类似于Ojective-C的内省方法,可以用 “==”检查某个类对象是否为指定类对象。
    如,

    // 检查e的Class对象是否为Employee类对象
    Employee e;
    if(e.getClass() == Employee.class) {
          ..
    }
    // 检查e的父对象是否为Object类对象
    Employee e;
    Class cl = e.getClass();
    Class supercl = cl.getSupperClass();
    
    if (supercl == Object.class) {
          // process e or cl
    }
    

    捕获异常

    如果类名对应类不存在,用forName()创建Class对象会抛出异常ClassNotFoundException。简单处理方式,如下

    try {
          Claass c1 = Class.forName("TestNotExistClass");
          // do sth. with c1
    }catch(Exception e) {
          e.printStackTrace();
    }
    

    反射分析类

    1. 字段类:Field,提供有关类或接口的单个字段的信息,以及对它的动态访问权限。反射的字段可能是一个类(静态)字段或实例字段;
    2. 方法类:Method;
    3. 构造器类:Constructor;
    4. 修饰符工具类:Modifiers;

    Field 字段类型类, Method 方法类型类, Constructor 构造器类

    java.lang.reflect包有三个类Field、Method、Constructor分别用于描述类的域(字段)、方法、构造器。
    三个类都有getName()方法,返回项名称。
    Field类有getType方法,返回域所属类型的Class对象。
    Method类和Constructor类有能够报告参数类型的方法。
    Method类getReturnType方法能报告函数返回值类型。

    Class常用方法

    Field[] getFields(); // 返回包含Field对象的数组,记录了类或其超类的公有域
    Field[] getDeclaredFields();  // 返回包含Field对象的数组,记录了类的所有域(不包括超类)
    
    Method getMethod(String name, Class<?>...  parameterTypes); // 返回指定名称 + 形参类型和数量的方法
    Method[] getMethods(); // 返回包含Method对象的数组,记录了类或其超类的公有方法
    Method[] getDeclaredMethods();  // 返回包含Method对象的数组,记录了类的所有方法(不包括超类)
    
    Constructor[] getConstructors(); // 返回包含Constructor对象的数组,记录了类或其超类的公有构造器
    Constructor[] getDeclareConstructor(); // 返回包含Constructor对象的数组,记录了类的所有构造器(不包括超类)
    
    Class getClass(); // 获取对象或者类的Class对象
    Class getSuperclass(); // 获取对象或者类的父类Class对象
    Class getName(); // 获取对象或类的名称(字符串)
    static Class forName(String); // 根据路径创建Class对象
    
    TypeVariable[] getTypeParameters(); // 获取声明的变量类型的数组
    
    int getModifiers(); // 获取对象或类的的Java语言修饰符(如public, static)使用情况
    
    

    Field、Method、Constructor常用方法

    Class getType(); (Field独有) // 获取域的类型
    Class getReturnType(); (Method独有) // 获取返回值类型
    Class[] getExceptionTypes(); (Constructor和Method类中) // 描述方法抛出的异常类型数组
    Class[] getParameterTypes(); (Constructor和Method类中) // 描述参数类型的Class对象数组
    Object get(Object obj); // 获取obj对象中Field对象表示的值, 调用对象是Field对象(即要获取的属性),obj对象是要查询的属性值所属类对象. 也就是说,返回obj.调用对象的Field值
    void set(Object obj, Object newValue); // 设置obj.调用对象所属Field = newValue
    
    Class getDeclaringClass(); // 返回用于描述类中定义的Constructor、Method或Field的类型
    Class getName(); // 获取名称
    int getModifiers(); // 获取修饰符使用情况
    void setAccessible(boolean ); // 修改反射对象的访问标志 (为调试、持久存储和相似机制提供的功能)
    boolean isAccessible(); // 查询反射对象可访问标志
    

    Modifiers 修饰符工具类

    Field、Method、Constructor三个类都有getModifiers方法,返回一个整型值,不同的位代表public static等修饰符使用情况。
    可以利用java.lang.reflect包中的Modifier类的静态分析getModifiers返回的整型值,如使用Modifier类的isPublic、isPrivate、isFinal判断方法或构造器是否是public、private或final。
    Modifiers.toString() 将修饰符打印出来。

    获取Field, Method, Constructor
    Class类的getFields、getMethods和getConstructors方法返回类的public 域、方法和构造器数组。(所有的public成员,包括超类的public成员)
    Class类的getDeclareFields、getDeclareMethods、getDeclareConstructorsf方法返回类类的所有域、方法和构造器数组。(所有的public、private、protected成员,但不包括超类的成员)

    Modifiers 常用方法

    static String toString(int modifiers); // 读取modifilers中设置的修饰符使用情况的字符串表示
    // 读取是否为函数名对应修饰符修饰
    static boolean isAbstract(int modifiers); 
    static boolean isFinal(int modifiers);
    static boolean isInterface(int modifiers);
    static boolean isNative(int modifiers);
    static boolean isPrivate(int modifiers);
    static boolean isProtected(int modifiers);
    static boolean isPublic(int modifiers);
    static boolean isStatic(int modifiers);
    static boolean isStrict(int modifiers);
    static boolean isSynchronized(int modifiers);
    static boolean isVolatile(int modifiers);
    

    运行时使用反射分析对象

    查看数据域实际内容

    先获得Class对象,再通过Class对象GetDeclaredField获得对应名称数据域,如果是私有访问权限,就利用Field/Method/Constructor的setAccessible方法修改访问权限控制属性。

    修改访问权限

    示例

    Employee harry = new Employee("Harry Hacker", 35000, 10, 1, 1989) ;
    Class cl = harry.getClass(); // cl 代表Employee类对象
    Field f = cl .getDeclaredField("name"); // 获取harry对象的name数据域
    Object v = f.get (harry) ;
    // 获取harry对象的name数据域的值,也就是说"Harry Hacker"字符串对象
    

    存在问题:如果"name"域是private访问权限,那么后面的get方法就会抛出异常,需要先修改访问权限。

    Field f = cl .getDeclaredField("name"); // 获取harry对象的name数据域
    f.setAccessible(true);
    Object v = f.get (harry) ;
    

    查看任意对象的内部信息

    ArrayList<Integer> squres = new ArrayList<>();
    for (int i = 1; i <= 5; i++) {
          squares.add(i);
    }
    
    System.out.println(new ObjectAnalyzer.toString(squares));
    

    使用反射编写泛型数组

    如何复制一个数组?为何要使用反射编写泛型数组?

    例如实现Arrays.copyOf()方法。常规做法,编写一个通用的程序:

    // Arrays.copyOf方法的使用
    Employee[] a = new Employee[100];
    // modify array a
    // 创建新数组(存储),并复制老的元素到数组a (将Employee[] 类型转化成了Object[]类型)
    a = Arrays.copyOf(a, 2*a.length); // 超出被复制数组长度部分用默认值0填充
    
    // 自定义copyOf方法 -- 存在问题的自定义方法
    public static Object[] badCopyOf(Object[] a, int newLength) {
          if(newLength <= 0) return new int[0];
          Object[] newArray = new Object[newLength];
          System.arraycopy(a, 0, newArray, Math.min(a.length, newLength));
          return newArray;
    }
    

    程序根据原有数组Employee[] a,新建一个指定长度新数组Object[] newArray,再将数组a的值全部复制到newArray中。
    看起来很美好,然而存在一个严重问题:新建的newArray是Object[]类型,即使强制转化成Employee[]类型,也不可能真正转化成Employee[],除非从一开始就是Employee[]中间可以临时转化成Object[]。而用户是不能直接根据Employee所包含的方法使用Object对象的,即使是强制转换也不行。

    改进:将a的类型信息也一并传入复制函数。根据参数创建指定类型数组,可以利用Array.newInstance()
    Object newArray = Array.newInstance(componentType, newLength);
    componentType: 要创建数组类型信息,通过Class对象的getComponentType()方法获得;
    newLength: 要创建数组长度,通过Array.getLength(a)获得;

    复制一个新的同类型的数组的步骤:

    1. 获得数组a的类对象(Class cl = a.getClass());
    2. 确认a是数组(Class的isArray方法);
    3. 获得数组a的类型信息(cl.getComponentType());
    4. 获得数组a的长度(Array.getLength(a));
    5. 创建指定类型和长度的数组(Array.newInstance(componentType, newLength));
    6. 复制旧数组元素到新数组(System.arraycopy());

    实现copyOf代码:

    public static Object copyOf(Object a, int newLength) {
          Class cl = a.getClass();
          if(!cl.isArray() return null; // 确认不是数组,就返回空
          Class componentType = cl.getComponentType();
          int length = Array.getLength(a); // 前提是a实际是一个数组
          Object newArray = Array.newArray(componentType, newLength);
          System.arrarycopy(a, 0, newArray, 0, Math.min(length, newLength));
          return newArray;
    }
    

    调用任意方法

    C/C++有指针执行任意函数,java没有方法指针,不过提供了接口,另外还可以通过反射机制调用任意方法。
    要调用的方法是invoke,原型:

    /**
    * 第一个参数obj是隐式参数(静态方法隐式参数可忽略),其余对象提供了显式参数(没有显示参数就用null)
    * invoke的参数和返回值必须是Object类型,1)设计风格复杂,类似于C;2)会经过多次类型转化,导致错过编译器类型检查,出错可能性较大(如提供invoke错误参数)
    */
    public Object invoke(Object obj, Object...args)
    public Object invoke(Object implicitParameter,Object[] explicitParamenters)
    

    使用示例:

    String n = (String)m1.invoke(harry);
    

    如果返回的是基本类型,invoke方法会自动返回其包装器类型。

    Method m1 = Employee.class.getMethod("getName"); // 无参数方法getName()
    Method m2 = Employee.class.getMethod("raiseSalary", double.class); // 包含一个形参 raiseSalary(double)
    String name = (String)m1.invoke(e1); // 返回Employee对象e1.getName() 结果
    m2.invoke(e2, 10.0); //调用e2.raiseSalary(double),为e2增加薪资
    
    Method m3 = Math.class.getMethod("max", int.class, int.class); // m3代表方法Math.max(int, int), 注意函数名和参数类型、数量都要对应上
    int max = (int)m3.invoke(null, 20, 40); // 静态方法invoke第一个参数传入null,后面的参数与取得实际m3所需要形参类型和数量对应上
    System.out.println("max number = " + max);
    

    小结

    反射要点

    • 获得对应Class对象
    • 通过Class对象调用getDeclaredFields获得对应Field
    • 利用反射查看编译时不清楚的对象域

    建议
    仅在必要时,才使用Method对象的回调功能(invoke),一般情况下最好使用接口即lambda表达式来调用方法。因为接口回调速度比使用Method对象更快,更容易维护。

  • 相关阅读:
    Socket.IO API Server
    Socket.IO 中文笔记
    Express 中文API 笔记
    JWT
    Sass 记录
    CSS高级技巧(二)背景和边框
    CSS高级技巧(一)常见的注意事项
    CSS进阶(二十四)流向的改变
    linux应用之test命令详细解析
    数字证书原理(ssl,https)
  • 原文地址:https://www.cnblogs.com/fortunely/p/14091812.html
Copyright © 2020-2023  润新知