Java反射,解剖对象那些事

概述

为什么要写关于Java反射反面的博客呢,原因背景是这样的,最近我们team在使用retrofit做数据请求和解析,但是如果服务端返回的数据不包含某些数据的时候,解析出来的数据为null,就算在定义的时候,给model对象进行了一次最初的赋值也不行,原因在于解析的时候,没有数据的字段被retrofit解析成了null,这下可好了,调用这些模型的null字段就会跪掉,好了,测试bug来了,于是针对这个问题,我们程序狗们得着手解决啦。最直接最傻逼的做法当然就是去挨着每个字断去判断咯,但是我操,这个数据多的一逼,不瞒大家说,考虑到风险的问题,我们还真的这么干了,至少能够解决问题,我们处理这个事情的同事要吐血了,哈哈。现在过了发布,准备重构一下,那么我想到了两个办法去解决这个问题,得治标治本,从源头下手,有两个办法,一个是将恢复出来的模型进行解剖,如果发现字断为null的就赋值为空字符串,另一个办法是从解析入手,覆盖retrofit的解析方法,自定义解析规则,一个一个的来吧,首先从解剖入手呗,于是又了今天这篇博客。

Java反射基础

先来看看反射的定义吧:JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法和属性;这种动态获取的信息以及动态调用对象的方法的功能称为java语言的反射机制。
为了解决问题去学习是最好的学习方法。问题放在这里,但是发现对发射不熟悉怎么办呢,OK,google一下,找到oracle官网,先来看一番吧,照抄翻译网页的事咱就不干了,摘抄点精华吧。

Java Reflction可操纵成员Members

官网看到了Members包括:Fields、Methods和Constructors,其实也就是包含了最基本的一个对象的组成成分了。

Fields

获取Field的类型,一个属性可以是基本数据类型,也可以是引用类型,最基本的八种数据类型分别是boolean,byte,short,int,long,char,float和double,一个引用类型或者都直接或者间接继承自Object,或者是接口interface,或者是数组Array,或者是列表List或枚举等。

1
2
Class<?> clazz = Class.forName("full path class name");
Class<?> clazz = object.getClass();

可以通过类全路径字符串描述或者对象的getClass方法来获取一个类。

1
2
3
4
clazz.getField("fieldname");// 获取某列属性
clazz.getDeclaredField("fieldname");//或者本对象不包括继承的某列属性
clazz.getFields(); //获取所有属性
clazz.getDeclaredFields(); //获取本对象不包括继承的属性

好了现在我们就获得了Field。

1
2
field.getType();//获取到类型
field.getGenericType();//获取到范型的类型,

这两个的区别在于,以一个List来说,前者返回的只是List.class而后者返回的则是List,后者得到的信息更多,我们解剖对象的时候,更小粒度的控制的时候就得使用这个Api。
获取描述符

1
int mods = field.getModifiers(); // 返回一个包涵描述符的整数

对mods进行解析就可以知道描述符了。
操作对象的数据,这部分才是数据操作的核心。它可以将一个运行着的对象的数据进行改变,叼不叼。
既然获取到了一列Field,那么接下来只需要两个条件就行了,一个是对象,即对谁赋值,然后是值是多少。
以一个Student为例子。

1
2
3
4
5
public class Student{
int age;
String name;
List<String> followers;
}

假设又了一个Student对象student,那么进行赋值操作如下

1
2
3
4
5
6
7
8
Student student = new Student();
Class<?> clazz = student.getClass();
Field ageField = clazz.getFieldDeclaredField("age");
Field nameField = clazz.getFieldDeclaredField("name");
Field followersField = clazz.getFieldDeclaredField("followers");
ageField.setInt(student,12);
nameField.set(student,"gordon rawe");
follows.set(student,Arrays.asList("nicole","saber"));

好了,到这里,列的基本操作就完成啦。

Methods

Methods区域的过后补充。

Constructors

Constructors区域的过后补充。

解剖数据

现在回到文章一开始提出的需求,解剖一个对象,将空的字符串进行赋默认值操作,思路是采用的递归,直到所有的数据类型都是基本类型或者String类型作为结束,由于我们只关心String,那么开始吧。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public static void replaceNullFieldsWithTarget(Class<?> clazz, Object t, String target) throws Exception {
Field[] fields = clazz.getDeclaredFields();
for (Field field : fields) {
if (field.getType() == String.class) {
if (field.get(t) == null) field.set(t, target);
} else if (field.getType() == List.class) {
List<Object> list = (List<Object>) field.get(t);
if (list != null && list.size() > 0) {
if (list.get(0).getClass() == String.class) {
List<String> newList = new ArrayList<>();
for (Object obj : list) {
newList.add(obj == null ? "" : obj.toString());
}
field.set(t, newList);
} else {
for (Object obj : list) {
replaceNullFieldsWithTarget(obj.getClass(), obj, target);
}
}
}
} else if (!field.getType().isPrimitive()) {
Object object = field.get(t);
replaceNullFieldsWithTarget(field.getType(), object, target);
}
}
}