大家好,今天程序员打V像往常一样辛苦地搬着砖,突然遇到一个问题。
我们有一个会员Member对象,然而会员对象有许多字段,比如说姓名,生日,邮箱,电话,微信号等等。因此,在创建会员对象时,我们需要初始化的字段非常多,需要连环调用setter来完成对象的初始化。
public Member createMember(JSONObject params) {
Member member = new Member();
// setter 夺命连环调用
member.setName(params.getString("name"));
member.setDateOfBirth(params.getDate("dateOfBirth"));
member.setEmail(params.getString("email"));
member.setPhone(params.getString("phone"));
member.setWechat(params.getString("wechat"));
return Member;
}
从上述代码可以看到,每设置一个字段需要调用一次setter,当字段数量达到十几种甚至以上的时候,调用大量setter将会严重降低代码的可读性,同时对于我们程序员来说,一直敲setter的代码想必也很累。
这个时候我们就需要问自己一个问题:
有没有一种方法,只需要写一次代码,就可以一劳永逸地自动初始化对象字段呢?
答案是有!那就是利用Java的反射机制。
这是利用Java反射机制动态创建对象并初始化字段的代码。
public class BuilderImpl implements Builder {
public <T> T buildWithParams(Class<T> clazz, JSONObject params) {
try {
Field[] fields = clazz.getDeclaredFields();
T instance = clazz.newInstance();
for (Field field : fields) {
String fieldName = field.getName();
String typeName = field.getType().getSimpleName();
String setterName = getSetterName(fieldName);
if (params.get(fieldName) == null) {
continue;
}
Method setter = null;
try {
setter = clazz.getMethod(setterName, field.getType());
} catch (Exception e) {
e.printStackTrace();
continue;
}
if (typeName.equals("int") || typeName.equals("Integer")) {
setter.invoke(instance, params.getIntValue(fieldName));
} else if (typeName.equals("boolean") || typeName.equals("Boolean")) {
setter.invoke(instance, params.getBoolean(fieldName));
} else if (typeName.equals("String")) {
setter.invoke(instance, params.getString(fieldName));
} else if (typeName.equals("Date")) {
setter.invoke(instance, params.getDate(fieldName));
} else if (typeName.equals("Double") || typeName.equals("double")) {
setter.invoke(instance, params.getDouble(fieldName));
} else if (typeName.equals("Float") || typeName.equals("float")) {
setter.invoke(instance, params.getFloat(fieldName));
} else if (typeName.equals("Short") || typeName.equals("short")) {
setter.invoke(instance, params.getShort(fieldName));
} else if (typeName.equals("Long") || typeName.equals("long")) {
setter.invoke(instance, params.getLong(fieldName));
} else if (typeName.equals("Byte") || typeName.equals("byte")) {
setter.invoke(instance, params.getByte(fieldName));
}
}
return instance;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
private String getSetterName(String fieldName) {
return "set" + fieldName.substring(0, 1).toUpperCase() + fieldName.substring(1);
}
}
这里简单说一下思路。首先所有的字段初始值都在参数params里面,params的类型是JSONObject,也就是类似map的数据结构,用key value来储存数据。
params里面的key名称需要与对象字段名称一样,这样才能找到相对应的字段。
首先我们传入所要创建对象的Class,以及含有字段初始值的params参数。接着利用Java的反射机制,创建一个对象实例。
这里需要注意的是,该类必须有默认构造函数,否则 newInstance() 将抛出异常。
再来利用反射机制,调用 getDeclaredFields() 获取所有字段 (Field)。这里值得注意的是,如果我们调用的是 getFields() ,则只会返回 public fields。必须调用 getDeclaredFields() 才能够得到 private fields。
然后我们需要遍历得到的所有字段,根据每个字段的名称利用反射机制去获得相应的 setter 方法。通常 setter 方法的名字格式为 set 加上字段名,字段名第一个字母大写。例如:
String name;
public void setName(String name);
如果该字段没有 setter 方法,则表示该字段不能被设置与修改,我们可以直接 continue 到下一个字段。
要利用反射机制获得 setter 方法,我们还需要提供该方法参数的类型,这个可以直接通过 field.getType() 获得。
由于我们直接从params里面get出来的值都是默认为Object类型,因此我们还需要判断该字段的类型,来对params里面的值进行转换。因为JSONObject本身已经提供了转型的方法给我们,所以我们直接调用对应的转型方法即可。例如:
// 判断字段类型为整型,从params取出参数时将类型转换为整型
if (typeName.equals("int") || typeName.equals("Integer")) {
setter.invoke(instance, params.getIntValue(fieldName));
}
当我们将所有字段都初始化之后,就可以返回我们的对象了。
虽然我们的上面利用Java反射机制初始化对象的代码较长,但是它的复用性很高,可以适用于任何对象的创建,也无所谓需要初始化的字段有多少个。
因此我们可以这样来优化我们之前的代码:
private Builder builder = new BuilderImpl();
public Member createMember(JSONObject params) {
// 无论有多少需要初始化的字段,都是一行代码搞定!
Member member = builder.buildWithParams(Member.class, params);
return Member;
}
就算有100个需要初始化的字段,那也都是一行代码就搞定!
这里提供一些编程术语的中英对照给有兴趣的朋友。
中 | 英 |
---|---|
Java反射 | Java Reflection |
构造函数 | constructor |
字段 | field |
实例 | instance |
对象 | object |
类型 | type |
参数 | parameters |