問題:實例對象需要初始化的字段太多
大家好,今天程序員打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 |