Java知识点—反射

简介: 今天说Java模块内容:反射。

一点事情


为了方便大家学习这个系列,我新建了一个github仓库,会更新Android体系架构所有文章,还有面试专题,思维导图链接等等,地址:


https://github.com/JiMuzz/Android-Architecture


也可以直接点击文末 阅读原文 查看。


前言


今天说Java模块内容:反射。


反射介绍


正常情况下,我们知晓我们要操作的类和对象是什么,可以直接操作这些对象中的变量和方法,比如一个User类:


User user=new User();
user.setName("Bob");


但是有的场景,我们无法正常去操作:


  • 只知道类路径,无法直接实例化的对象。
  • 无法直接操作某个对象的变量和方法,比如私有方法,私有变量。
  • 需要hook系统逻辑,比如修改某个实例的参数。


等等情况。


所以我们就需要一种机制能让我们去操作任意的类和对象。


这种机制,就是反射。简单的说,反射就是:


对于任意一个,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性。


常用API举例


先设定一个User类:


package com.example.testapplication.reflection;
public class User {
    private int age;
    public String name;
    public User() {
        System.out.println("调用了User()");
    }
    private User(int age, String name) {
        this.name = name;
        this.age = age;
        System.out.println("调用了User(age,name)"+"__age:"+age+"__name:"+name);
    }
    public User(String name) {
        this.name = name;
        System.out.println("调用了User(name)"+"__name:"+name);
    }
    private String getName() {
        System.out.println("调用了getName()");
        return this.name;
    }
    private String setName(String name) {
     this.name = name;
        System.out.println("调用了setName(name)__"+name);
        return this.name;
    }
    public int getAge() {
        System.out.println("调用了getAge()");
        return this.age;
    }    
}


获取Class对象


主要有三种方法获取Class对象


  • 根据类路径获取类对象
  • 直接获取
  • 实例对象的getclass方法


//1、根据类路径获取类对象
try {
    Class clz = Class.forName("com.example.testapplication.reflection.User");
} catch (ClassNotFoundException e) {
    e.printStackTrace();
}
//2、直接获取
Class clz = User.class;
//3、对象的getclass方法
Class clz = new User().getClass();


获取类的构造方法


1、获取类所有构造方法


Class clz = User.class;
//获取所有构造函数(不包括私有构造方法)
Constructor[] constructors1 = clz.getConstructors();
//获取所有构造函数(包括私有构造方法)
Constructor[] constructors2 = clz.getDeclaredConstructors();


2、获取类的单个构造方法


try {
        //获取无参构造函数
        Constructor constructor1 = clz.getConstructor();
        //获取参数为String的构造函数
        Constructor constructor2 =clz.getConstructor(String.class);
        //获取参数为int,String的构造函数
        Class[] params = {int.class,String.class};
        Constructor constructor3 =clz.getDeclaredConstructor(params);
    } catch (NoSuchMethodException e) {
        e.printStackTrace();
    }


需要注意的是,User(int age, String name)为私有构造方法,所以需要使用getDeclaredConstructor获取。


调用类的构造方法生成实例对象


1、调用Class对象的newInstance方法


这个方法只能调用无参构造函数,也就是Class对象的newInstance方法不能传入参数。


Object user = clz.newInstance();


2、调用Constructor对象的newInstance方法


Class[] params = {int.class,String.class};
Constructor constructor3 =clz.getDeclaredConstructor(params);
constructor3.setAccessible(true);
constructor3.newInstance(22,"Bob");


这里要注意下,虽然getDeclaredConstructor能获取私有构造方法,但是如果要调用这个私有方法,需要设置setAccessible(true)方法,否则会报错:


can not access a member of class com.example.testapplication.reflection.User with modifiers "private"


获取类的属性(包括私有属性)


Class clz = User.class;
Field field1 = clz.getField("name");
Field field2 = clz.getDeclaredField("age");


同样的,getField获取public类变量,getDeclaredField可以获取所有变量(包括私有变量属性)。


所以一般直接用getDeclaredField即可。


修改实例的属性


接上例,获取类的属性后,可以去修改类实例的对应属性,比如我们有个user的实例对象,我们来修改它的name和age。


//修改name,name为public属性
Class clz = User.class;
Field field1 = clz.getField("name");
field1.set(user,"xixi");
//修改age,age为private属性
Class clz = User.class;
Field field2 = clz.getDeclaredField("age");
field2.setAccessible(true);
field2.set(user,123);


获取类的方法(包括私有方法)


//获取getName方法
    Method method1 = clz.getDeclaredMethod("getName");
 //获取setName方法,带参数
    Method method2 = clz.getDeclaredMethod("setName", String.class);
    //获取getage方法
    Method method3 = clz.getMethod("getAge");


调用实例的方法


method1.setAccessible(true);
Object name = method1.invoke(user);
method2.setAccessible(true);
method2.invoke(user, "xixi");
Object age = method3.invoke(user);


反射优缺点


虽然反射很好用,增加了程序的灵活性,但是也有他的缺点:


  • 性能问题。由于用到动态类型(运行时才检查类型),所以反射的效率比较低。但是对程序的影响比较小,除非对性能要求比较高。所以需要在两者之间平衡。
  • 不够安全。由于可以执行一些私有的属性和方法,所以可能会带来安全问题。
  • 不易读写。当然这一点也有解决方案,比如jOOR库,但是不适用于Android定义为final的字段。


Android中的应用


插件化(Hook)


Hook 技术又叫做钩子函数,在系统没有调用该函数之前,钩子程序就先捕获该消息,钩子函数先得到控制权,这时钩子函数既可以加工处理(改变)该函数的执行行为,还可以强制结束消息的传递。


在插件化中,我们需要找到可以hook的点,然后进行一些插件的工作,比如替换Activity,替换mH等等。这其中就用到大量反射的知识,这里以替换mH为例:


// 获取到当前的ActivityThread对象
Class<?> activityThreadClass = Class.forName("android.app.ActivityThread");
Field currentActivityThreadField = activityThreadClass.getDeclaredField("sCurrentActivityThread");
currentActivityThreadField.setAccessible(true);
Object currentActivityThread = currentActivityThreadField.get(null);
//获取这个对象的mH
Field mHField = activityThreadClass.getDeclaredField("mH");
mHField.setAccessible(true);
Handler mH = (Handler) mHField.get(currentActivityThread);
//替换mh为我们自己的HandlerCallback
Field mCallBackField = Handler.class.getDeclaredField("mCallback");
mCallBackField.setAccessible(true);
mCallBackField.set(mH, new MyActivityThreadHandlerCallback(mH));


动态代理


动态代理的特点是不需要提前创建代理对象,而是利用反射机制在运行时创建代理类,从而动态实现代理功能。


public class InvocationTest implements InvocationHandler {
    // 代理对象(代理接口)
    private Object subject;
    public InvocationTest(Object subject) {
        this.subject = subject;
    }
    @Override
    public Object invoke(Object object, Method method, Object[] args)
            throws Throwable {
        //代理真实对象之前
        Object obj = method.invoke(subject, args);
        //代理真实对象之后
        return obj;
    }
}


三方库(注解)


我们可以发现很多库都会用到注解,而获取注解的过程也会有反射的过程,比如获取Activity中所有变量的注解:


public void getAnnotation(Activity activity){
    Class clazz = activity.getClass();
    //获得activity中的所有变量
    Field[] fields = clazz.getDeclaredFields();
    for (Field field : fields) {
        field.setAccessible(true);
        //获取变量上加的注解
        MyAnnotation test = field.getAnnotation(MyAnnotation.class);
        //...
    }
}


这种通过反射处理注解的方式称作运行时注解,也就是程序运行状态的时候才会去处理注解。但是上文说过了,反射会在一定程度上影响到程序的性能,所以还有一种处理注解的方式:编译时注解。


所用到的注解处理工具是APT


APT是一种注解处理器,可以在编译时进行扫描和处理注解,然后生成java代码文件,这种方法对比反射就能比较小的影响到程序的运行性能。


这里就不说APT的使用了,下次会专门有章节提到~


Android体系架构


Android体系架构:https://github.com/JiMuzz/Android-Architecture


参考


https://www.jianshu.com/p/3382cc765b39 


https://segmentfault.com/a/1190000015860183

目录
相关文章
|
3月前
|
安全 Java 编译器
揭秘JAVA深渊:那些让你头大的最晦涩知识点,从泛型迷思到并发陷阱,你敢挑战吗?
【8月更文挑战第22天】Java中的难点常隐藏在其高级特性中,如泛型与类型擦除、并发编程中的内存可见性及指令重排,以及反射与动态代理等。这些特性虽强大却也晦涩,要求开发者深入理解JVM运作机制及计算机底层细节。例如,泛型在编译时检查类型以增强安全性,但在运行时因类型擦除而丢失类型信息,可能导致类型安全问题。并发编程中,内存可见性和指令重排对同步机制提出更高要求,不当处理会导致数据不一致。反射与动态代理虽提供运行时行为定制能力,但也增加了复杂度和性能开销。掌握这些知识需深厚的技术底蕴和实践经验。
78 2
|
27天前
|
存储 Java
[Java]反射
本文详细介绍了Java反射机制的基本概念、使用方法及其注意事项。首先解释了反射的定义和类加载过程,接着通过具体示例展示了如何使用反射获取和操作类的构造方法、方法和变量。文章还讨论了反射在类加载、内部类、父类成员访问等方面的特殊行为,并提供了通过反射跳过泛型检查的示例。最后,简要介绍了字面量和符号引用的概念。全文旨在帮助读者深入理解反射机制及其应用场景。
19 0
[Java]反射
|
4月前
|
存储 算法 安全
Java面试题:Java内存模型及相关知识点深度解析,Java虚拟机的内存结构及各部分作用,详解Java的垃圾回收机制,谈谈你对Java内存溢出(OutOfMemoryError)的理解?
Java面试题:Java内存模型及相关知识点深度解析,Java虚拟机的内存结构及各部分作用,详解Java的垃圾回收机制,谈谈你对Java内存溢出(OutOfMemoryError)的理解?
72 0
|
2月前
|
安全 Java 索引
Java——反射&枚举
本文介绍了Java反射机制及其应用,包括获取Class对象、构造方法、成员变量和成员方法。反射允许在运行时动态操作类和对象,例如创建对象、调用方法和访问字段。文章详细解释了不同方法的使用方式及其注意事项,并展示了如何通过反射获取类的各种信息。此外,还介绍了枚举类型的特点和使用方法,包括枚举的构造方法及其在反射中的特殊处理。
65 9
Java——反射&枚举
|
1月前
|
安全 Java 测试技术
🌟Java零基础-反射:从入门到精通
【10月更文挑战第4天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
26 2
|
2月前
|
安全 Java API
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
String常量池、String、StringBuffer、Stringbuilder有什么区别、List与Set的区别、ArrayList和LinkedList的区别、HashMap底层原理、ConcurrentHashMap、HashMap和Hashtable的区别、泛型擦除、ABA问题、IO多路复用、BIO、NIO、O、异常处理机制、反射
【Java面试题汇总】Java基础篇——String+集合+泛型+IO+异常+反射(2023版)
|
1月前
|
IDE Java 编译器
java的反射与注解
java的反射与注解
16 0
|
1月前
|
安全 Java 编译器
Java基础-知识点(二)
Java基础-知识点(二)
13 0
|
1月前
|
存储 缓存 安全
Java基础-知识点(一)
Java基础-知识点(一)
17 0
|
2月前
|
Java 程序员 编译器
Java的反射技术reflect
Java的反射技术允许程序在运行时动态加载和操作类,基于字节码文件构建中间语言代码,进而生成机器码在JVM上执行,实现了“一次编译,到处运行”。此技术虽需更多运行时间,但广泛应用于Spring框架的持续集成、动态配置及三大特性(IOC、DI、AOP)中,支持企业级应用的迭代升级和灵活配置管理,适用于集群部署与数据同步场景。
下一篇
无影云桌面