欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Android -从浅到懂使用反射机制

程序员文章站 2022-03-07 22:03:02
定义JAVA反射机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法;对于任何一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。使用场景反射是在运行时获取确定类型,绑定对象。常见的两个使用场景运行时获取对象的所有信息泛型擦除在运行时获取类这里的运行时指的是程序在运行后。相应的还有编译时,编译时是编译器将源代码翻译成机器能识别的代码。举个例子说明编译时和运行时的区别:微信图片list1 没有声明...

定义

JAVA反射机制是在运行状态中,对于任何一个类,都能够知道这个类的所有属性和方法;
对于任何一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为 Java 语言的反射机制。

使用场景

反射是在运行时获取确定类型,绑定对象。

常见的两个使用场景

  1. 运行时获取对象的所有信息
  2. 泛型擦除

在运行时获取类

这里的运行时指的是程序在运行后。相应的还有编译时,编译时是编译器将源代码翻译成机器能识别的代码。

举个例子说明编译时和运行时的区别:
Android -从浅到懂使用反射机制

泛型擦除

val list = arrayListOf<Int>(1,2,3)
val clz = Class.forName("java.util.ArrayList")
val method = clz.getMethod("add", Object::class.java)
method.invoke(list,"hhh")
Log.e(TAG,"list:${list.toString()}")

输出:

list:[1, 2, 3, hhh]

反射的利弊

  • 运行时类型的判断
  • 动态类加载,动态代理使用反射。

  • 性能问题,反射相当于一系列解释操作。
  • JVM 不会去优化这部分代码
  • 代码不易阅读

为什么会出现性能问题?

  1. Method#invoke 方法会对参数做封装和解封操作
  2. 需要检查方法可见性
  3. 需要校验参数
  4. 反射方法难以内联
  5. JVM 无法优化: Java 文档中介绍

invoke方法源码:

class Class {
    @CallerSensitive
    public Method getMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        Objects.requireNonNull(name);
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // 1. 检查方法权限
            checkMemberAccess(sm, Member.PUBLIC, Reflection.getCallerClass(), true);
        }
        // 2. 获取方法
        Method method = getMethod0(name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(methodToString(name, parameterTypes));
        }
        // 3. 返回方法的拷贝
        return getReflectionFactory().copyMethod(method);
    }

    @CallerSensitive
    public Method getDeclaredMethod(String name, Class<?>... parameterTypes)
        throws NoSuchMethodException, SecurityException {
        Objects.requireNonNull(name);
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // 1. 检查方法权限
            checkMemberAccess(sm, Member.DECLARED, Reflection.getCallerClass(), true);
        }
        // 2. 获取方法
        Method method = searchMethods(privateGetDeclaredMethods(false), name, parameterTypes);
        if (method == null) {
            throw new NoSuchMethodException(methodToString(name, parameterTypes));
        }
        // 3. 返回方法的拷贝
        return getReflectionFactory().copyMethod(method);
    }
}

如何使用

想要使用反射机制,就必须要先获取到该类的字节码文件对象(.class),通过字节码文件对象,就能够通过该类中的方法获取到我们想要的所有信息(方法,属性,类名,父类名,实现的所有接口等等),每一个类对应着一个字节码文件也就对应着一个Class类型的对象。

获取字节码文件对象

  • 1、根据类名:类名.class
  • 2、根据对象:对象.getClass()
  • 3、根据全限定类名:Class.forName(全限定类名)

常用的是第一种。

获取构造函数

构造函数分为两种:带参和不带参

  • 获取不带参构造函数
Class classs = Class.forName("com.xxx.xxx.User");
Constructor constructor = classs.getDeclaredConstructor();
Object user = constructor.newInstance();
  • 获取带参构造函数
Class classs = Class.forName("com.xxx.xxx.User");
Constructor constructor = classs.getConstructor(int.class,String.class);
Object user = constructor.newInstance(1,"张三");

获取成员变量

成员变量一般为公有和私有

  • 获取公有成员变量
Class classs = Class.forName("com.xxx.xxx.User");
Constructor constructor = classs.getConstructor();
Object user = constructor.newInstance();
Field declaredField = classs.getField("userName");
// 赋新值
declaredField.set(user, "李四");
  • 获取私有成员变量
Class classs = Class.forName("com.xxx.xxx.User");
Constructor constructor = classs.getConstructor();
Object user = constructor.newInstance();
Field declaredField = classs.getDeclaredField("userName");
// 因为属性是私有的,所有需要打开可见权限
declaredField.setAccessible(true);
// 赋新值
declaredField.set(user, "李四");

获取方法

成员方法一般为公有和私有,同时方法也分有参和无参

  • 获取公有成员无参方法
Class classs = Class.forName("com.xxx.xxx.User");
Constructor constructor = classs.getConstructor();
Object user = constructor.newInstance();
Method method = classs.getMethod("getUserName");
method.invoke(user);
  • 获取公有成员有参方法
Class classs = Class.forName("com.xxx.xxx.User");
Constructor constructor = classs.getConstructor();
Object user = constructor.newInstance();
Method method = classs.getMethod("getUserName", String.class);
method.invoke(user, "王五");
  • 获取私有成员无参方法
Class classs = Class.forName("com.xxx.xxx.User");
Constructor constructor = classs.getConstructor();
Object user = constructor.newInstance();
Method method = classs.getDeclaredMethod("getUserName");
// 因为属性是私有的,所有需要打开可见权限
method.setAccessible(true);
method.invoke(user);
  • 获取私有成员有参方法
Class classs = Class.forName("com.xxx.xxx.User");
Constructor constructor = classs.getConstructor();
Object user =  constructor.newInstance();
Method method = classs.getDeclaredMethod("getUserName",String.class);
// 因为属性是私有的,所有需要打开可见权限
method.setAccessible(true);
method.invoke(user, "王五");

Tip

  1. 一般来说,构造函数大部分是 public ,而成员变量和方法大部分是 private。public 权限直接正常调用即可、private需要在调用前增加 setAccessible(true)
  2. 获取构造函数、成员变量、成员方法都有一个获取所有的方法,根据需要去调用。如:getConstructor:获取单个构造函数,getConstructors:获取所有构造函数

总结

反射的作用是在运行时动态获取对象信息。

实现步骤:

  1. 获取 Class:有三种方式,常用的是 Class.forName("类全名称")
  2. 获取构造函数:通过 Class 对象调用 如:clz.getConstructor() 得到构造函数
  3. 创建对象:通过构造函数创建对象 如:con.newInstance()

没有 Declared 前缀会返回所有的 public 方法,包括父类的方法。
而加 Declared 前缀会返回所有自己定义的方法,public,protected,private 都在此,但是不包括父类的方法。

这也正是 getMethodgetDeclaredMethod 的区别。

  • 获取私有构造函数
Class classs = Class.forName("com.xxx.xxx.User");
Constructor constructor = classs.getDeclaredConstructor();
// 因为属性是私有的,所有需要打开可见权限
constructor.setAccessible(true);
Object user = constructor.newInstance();

  • 获取私有成员变量
...
Field declaredField = classs.getDeclaredField("userName");
// 因为属性是私有的,所有需要打开可见权限
declaredField.setAccessible(true);

  • 获取私有成员方法
...
Method method = classs.getDeclaredMethod("getUserName");
// 因为属性是私有的,所有需要打开可见权限
method.setAccessible(true);

反射耗时的原因:

  • invoke方法需要做封装和解封操作
  • 需要类型检查、权限检查
  • 需要校验参数
  • 需要检查方法的可见性
  • JVM无法做优化

参考

本文地址:https://blog.csdn.net/ITxiaodong/article/details/110642378

相关标签: Android