利用Java反射機制,創建與初始化字段較多的對象

問題:實例對象需要初始化的字段太多

大家好,今天程序員打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
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章