聊聊Java知識點之反射
前言
今天說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
本文轉載自微信公眾號「碼上積木」,可以通過以下二維碼關注。轉載本文請聯系碼上積木公眾號。