利用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

你可能感兴趣的:(Java,后端,设计模式)