今天看啥  ›  专栏  ›  江湖非良人

反射与简单Java类

江湖非良人  · 简书  ·  · 2019-08-01 13:30

传统属性赋值弊端

  简单Java类主要是由属性组成,并且提供有相应的setter、getter处理方法,同时简单Java类最大的特征就是通过对象保存相应的类属性内容。但是如果使用传统的简单Java类开发,那么也会面临非常麻烦的困难。
范例:传统的简单Java类操作

@lombok.Data
class Emp{
    private String ename;
    private String job;
}

特别强调,为了方便理解,本次Emp类中定义的ename、job两个属性的类型使用的都是String类型。按照传统的做法,此时应该首先实例化Emp类的对象,而后通过实例化对象进行setter方法的调用以设置属性内容。

范例:传统的调用

public class JavaAPIDemo {
    public static void main(String[] args)throws Exception{
        Emp emp=new Emp();//更多情况下开发者关注的是内容的设置
        emp.setEname("张三");
        emp.setJob("客服");
        //使用为对象设置之后
        System.out.printf("姓名:%s、职位:%s",emp.getEname(),emp.getJob());
    }
}

在整个进行Emp对象实例化并设置数据的操作过程中,设置数据的部分是最麻烦的,但如果Emp类中有50个属性,那么对于整个程序将会出现一堆的setter方法调用。在实际的开发中,简单Java类的个数是非常多的,那么如果所有的简单的Java类都涉及到属性赋值的时候,这种情况代码的编码的重复性将非常的高。

  按照传统的直观的编程方式,所带来的问题就是代码会存在大量的重复操作,如果要想解决对象的重复处理操作那么唯一的解决方案就是反射机制,反射机制最大的特征是可以根据其自身的特点(Object类直接操作,可以直接操作属性或方法)实现相同功能类的重复操作的抽象处理。

属性自动设置解决方案

  经过分析后已经确认了当前简单Java类操作的问题所在,而对于开发者而言就需要想办法通过一种解决方案来实现属性内容的自动设置,那么这时的设置强烈建议采用字符串的形式来描述对应的类型。
1、在进行程序开发时String字符串可以描述的内容有很多,并且也可以由开发者自行定义字符串的结构,下面就采用“属性名:值|属性名:值|属性名:值”的形式来为简单Java类中的属性初始化。
2、类设计的基本结构:应该由一个专门的ClassInstanceFactory类负责所有的反射处理,即:接受反射对象与要设置的属性内容,同时可以获取指定类的实例化对象。

反射与简单Java类

3、设计的基本结构:

public class JavaAPIDemo {
    public static void main(String[] args)throws Exception{
        String value="ename:张三|job:客服";
        Emp emp=ClassInstanceFactory.create(Emp.class,value);
        System.out.printf("姓名:%s、职位:%s",emp.getEname(),emp.getJob());
    }
}
class ClassInstanceFactory{
    private ClassInstanceFactory(){}

    /**
     * 实例化对象的创建方法,该对象可以根据传入的字符串结构:"属性名:值|属性名:值|属性名:值"
     * @param clazz 要进行反射实例化的Class对象,有Class就可以反射实例化对象
     * @param value 要设置给对象的属性内容,字符串结构:"属性名:值|属性名:值|属性名:值"
     * @return 一个已经配置好属性内容的Java对象
     */
    public static <T> T create(Class<?> clazz,String value){
        return null;
    }
}

那么在当前的开发中,所需要完善的就是ClassInstanceFactory.create()处理方法。

单级属性配置

  对于此时的Emp类中会发现所给出的数据类型都没有其他的引用关联了,只是描述了Empty本类的对象,所以这样的设置成为单级设置处理,所以此时应该处理两件事情:

  • 需要通过反射进行指定类对象的实例化处理;
  • 进行内容的设置(Field属性类型、方法名称、要设置的内容);

1、定义StringUtils实现首字母大写功能

class StringUtils {
    public static String initcap(String str) {
        if (str == null || "".equals(str)) {
            return str;
        }
        if (str.length() == 1) {
            return str.toUpperCase();
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
}

2、定义BeanUtils工具类,该工具类主要实现属性的设置。

class BeanUtils{
    private BeanUtils(){}
    /**
     * 实现指定对象的属性设置
     * @param obj 要进行反射操作的实例化对象
     * @param value 包含有指定内容的字符串,格式"属性名:值|属性名:值|属性名:值"
     */
    public static void setValue(Object obj,String value){
        String[] results=value.split("\\|");//按照"|"进行每一组属性的拆分
        String[] attrs;
        Field field;
        Method method;
        for (String result : results) {
            attrs=result.split(":");
            try {
                field=obj.getClass().getDeclaredField(attrs[0]);
                method=obj.getClass().getDeclaredMethod("set"+StringUtils.initcap(attrs[0]),field.getType());
                method.invoke(obj,attrs[1]);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}

3、ClassInstanceFactory负责实例化对象并且调用BeanUtils类实现属性内容的设置。

class ClassInstanceFactory{
    private ClassInstanceFactory(){}
    /**
     * 实例化对象的创建方法,该对象可以根据传入的字符串结构:"属性名:值|属性名:值|属性名:值"
     * @param clazz 要进行反射实例化的Class对象,有Class就可以反射实例化对象
     * @param value 要设置给对象的属性内容,字符串结构:"属性名:值|属性名:值|属性名:值"
     * @return 一个已经配置好属性内容的Java对象
     */
    public static <T> T create(Class<?> clazz,String value){
        //如果想采用反射进行简单Java类对象属性设置时,类中必须要有无参构造
        try {
            Object obj=clazz.getDeclaredConstructor().newInstance();
            BeanUtils.setValue(obj,value);
            return (T)obj;
        } catch (Exception e) {
            e.printStackTrace();//如果此时真的出现了错误,本质上抛出异常也没用
            return null;
        }
    }
}

即使类中的属性再多,那么也可以轻松的实现setter的调用(类对象实例化处理)。

设置多种数据类型

  现在已经成功的实现了单级的属性配置,但是这里面依然需要考虑一个实际的情况:当前所给定的数据类型只是String。在实际的开发中面对简单Java类中的属性类型一般的可选为:long(Long)、int(Integer)、double(Double)、String、Date(日期、日期时间),所以这时对于当前的程序代码必须做出修改,要求可以实现各种数据类型的配置。
  既然要求可以实现不同类型的内容设置,并且BeanUtils主要是完成属性赋值处理的,那么就可以在这个类中追加一系列的处理方法。

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
public class JavaAPIDemo {
    public static void main(String[] args)throws Exception{
        String value="empno:1001|ename:张三|job:客服|salary:750.25|hireDate:2001-09-11";
        Emp emp=ClassInstanceFactory.create(Emp.class,value);
        System.out.printf("编号:%s、姓名:%s、职位:%s、薪资:%s、受雇日期:%s",emp.getEmpno(),emp.getEname(),emp.getJob(),emp.getSalary(),emp.getHireDate());
    }
}
class ClassInstanceFactory{
    private ClassInstanceFactory(){}
    /**
     * 实例化对象的创建方法,该对象可以根据传入的字符串结构:"属性名:值|属性名:值|属性名:值"
     * @param clazz 要进行反射实例化的Class对象,有Class就可以反射实例化对象
     * @param value 要设置给对象的属性内容,字符串结构:"属性名:值|属性名:值|属性名:值"
     * @return 一个已经配置好属性内容的Java对象
     */
    public static <T> T create(Class<?> clazz,String value){
        //如果想采用反射进行简单Java类对象属性设置时,类中必须要有无参构造
        try {
            Object obj=clazz.getDeclaredConstructor().newInstance();
            BeanUtils.setValue(obj,value);
            return (T)obj;
        } catch (Exception e) {
            e.printStackTrace();//如果此时真的出现了错误,本质上抛出异常也没用
            return null;
        }
    }
}
class StringUtils {
    public static String initcap(String str) {
        if (str == null || "".equals(str)) {
            return str;
        }
        if (str.length() == 1) {
            return str.toUpperCase();
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
}
class BeanUtils{
    private BeanUtils(){}
    /**
     * 实现指定对象的属性设置
     * @param obj 要进行反射操作的实例化对象
     * @param value 包含有指定内容的字符串,格式"属性名:值|属性名:值|属性名:值"
     */
    public static void setValue(Object obj,String value){
        String[] results=value.split("\\|");//按照"|"进行每一组属性的拆分
        String[] attrs;
        Field field;
        Method method;
        Object val;
        for (String result : results) {
            attrs=result.split(":");
            try {
                field=obj.getClass().getDeclaredField(attrs[0]);
                method=obj.getClass().getDeclaredMethod("set"+StringUtils.initcap(attrs[0]),field.getType());
                val= convertAttrValue(field.getType(),attrs[1]);
                method.invoke(obj,val);
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
    /**
     * 实现类型转换处理
     * @param clazz 属性类型,通过Field获取
     * @param value 属性的内容,传入的都是字符串,需要将其变为指定类型
     * @return 转换后的数据类型
     */
    private static Object convertAttrValue(Class<?> clazz, String value)throws Exception{
        String type=clazz.getName();
        switch (type){
            case "java.lang.Long":
            case "long":
                return Long.valueOf(value);
            case "java.lang.Integer":
            case "int":
                return Integer.valueOf(value);
            case "java.lang.Double":
            case "double":
                return Double.valueOf(value);
            case "java.util.Date":
                String pattern=null;
                if (value.matches("\\d{4}-\\d{2}-\\d{2}")) {
                    pattern = "yyyy-MM-dd";
                } else if (value.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")) {
                    pattern = "yyyy-MM-dd HH:mm:ss";
                }
                if (pattern != null) {
                    try {
                        return new SimpleDateFormat(pattern).parse(value);
                    } catch (Exception e) {

                    }
                }
                return new Date();
//            case "java.lang.String":
//                return value;
        }
        return value;
    }
}
class Emp{
    private Long empno;
    private String ename;
    private String job;
    private Double salary;
    private Date hireDate;
    //getter、setter

此时只是列举了常用几种数据类型,如果要将其作为产品推广,必须要考虑所有可能出现的类型,包括所有可能出现的日期格式。

级联对象实例化

  如果现在给定的类对象中存在有其它的引用的级联关系的情况下,称为多级设置。例如:一个雇员属于一个部门,一个部分属于一个公司,所以这时对于简单Java类的基本关系定义如下:

@lombok.Data
class Company{
    private String name;
    private Date createdate;
}
@lombok.Data
class Dept{
    private String dname;
    private String loc;
    private Company company;
}
@lombok.Data
class Emp{
    private Long empno;
    private String ename;
    private String job;
    private Double salary;
    private Date hireDate;
    private Dept dept;
}

如果要通过Emp进行操作,则应该使用“.”作为级联关系的处理:

  • dept.dname:客服部=Emp对象.getDept().setDname("客服部")
  • dept.company.name=Emp对象.getDept().getCompany().setName("MLDN")
    但是考虑到代码的简洁性,所以应该考虑通过级联的配置自动实现类中属性的实例化。
String value="empno:1001|ename:张三|job:客服|salary:750.25|hireDate:2001-09-11|dept.dname:客服部|dept.company.name:MLDN";

现在的属性存在有多级的关系,那么对于多级的关系就必须与单级的配置区分开

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.Date;
public class JavaAPIDemo {
    public static void main(String[] args)throws Exception{
        String value="empno:1001|ename:张三|job:客服|salary:750.25|hireDate:2001-09-11|dept.dname:客服部|dept.company.name:MLDN";
        Emp emp=ClassInstanceFactory.create(Emp.class,value);
        System.out.printf("编号:%s、姓名:%s、职位:%s、薪资:%s、受雇日期:%s",emp.getEmpno(),emp.getEname(),emp.getJob(),emp.getSalary(),emp.getHireDate());
    }
}
class ClassInstanceFactory{
    private ClassInstanceFactory(){}
    /**
     * 实例化对象的创建方法,该对象可以根据传入的字符串结构:"属性名:值|属性名:值|属性名:值"
     * @param clazz 要进行反射实例化的Class对象,有Class就可以反射实例化对象
     * @param value 要设置给对象的属性内容,字符串结构:"属性名:值|属性名:值|属性名:值"
     * @return 一个已经配置好属性内容的Java对象
     */
    public static <T> T create(Class<?> clazz,String value){
        //如果想采用反射进行简单Java类对象属性设置时,类中必须要有无参构造
        try {
            Object obj=clazz.getDeclaredConstructor().newInstance();
            BeanUtils.setValue(obj,value);
            return (T)obj;
        } catch (Exception e) {
            e.printStackTrace();//如果此时真的出现了错误,本质上抛出异常也没用
            return null;
        }
    }
}
class StringUtils {
    public static String initcap(String str) {
        if (str == null || "".equals(str)) {
            return str;
        }
        if (str.length() == 1) {
            return str.toUpperCase();
        }
        return str.substring(0, 1).toUpperCase() + str.substring(1);
    }
}
class BeanUtils{
    private BeanUtils(){}
    /**
     * 实现指定对象的属性设置
     * @param obj 要进行反射操作的实例化对象
     * @param value 包含有指定内容的字符串,格式"属性名:值|属性名:值|属性名:值"
     */
    public static void setValue(Object obj,String value){
        String[] results=value.split("\\|"), attrs,tmps;//按照"|"进行每一组属性的拆分
        Field field,tmpField;
        Method setMethod,getMethod,tmpMethod;
        Object val,currentObj,tmpObject;
        String attr,tmpAttr;
        for (String result : results) {
            attrs=result.split(":");
            attr=attrs[0];
            try {
                if(attr.contains(".")){
                    tmps=attr.split("\\.");
                     currentObj=obj;
                    //最后一位肯定是指定类中的属性名称,所以不在本次实例化处理的范畴之内
                    for (int i = 0,l=tmps.length-1; i <l ; i++) {//实例化
                        //调用相应的getter方法
                        tmpAttr=tmps[i];
                        getMethod=getDeclaredMethod(currentObj.getClass(),"get",tmpAttr);
                        tmpObject=getMethod.invoke(currentObj);
                        if (tmpObject == null) {
                            //该对象并没有实例化
                            tmpField = currentObj.getClass().getDeclaredField(tmpAttr);//获取属性类型
                            tmpMethod = getDeclaredMethod(currentObj.getClass(), "set", tmpAttr, tmpField.getType());
                            Object newObj = tmpField.getType().getConstructor().newInstance();
                            tmpMethod.invoke(currentObj, newObj);
                            currentObj = newObj;
                        }else{
                            currentObj=tmpObject;
                        }
                    }
                }else{
                    field=obj.getClass().getDeclaredField(attr);
                    setMethod=getDeclaredMethod(obj.getClass(),"set",attr,field.getType());
                    val= convertAttrValue(field.getType(),attrs[1]);
                    setMethod.invoke(obj,val);
                }
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    private static Method getDeclaredMethod(Class<?> clazz,String methodNamePrefix,String methodName,Class<?>... parameterTypes){
        try {
            if (methodNamePrefix == null || "".equals(methodNamePrefix)) {
                return clazz.getDeclaredMethod(methodName, parameterTypes);
            }
            return clazz.getDeclaredMethod(methodNamePrefix + StringUtils.initcap(methodName), parameterTypes);
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
    }
    /**
     * 实现类型转换处理
     * @param clazz 属性类型,通过Field获取
     * @param value 属性的内容,传入的都是字符串,需要将其变为指定类型
     * @return 转换后的数据类型
     */
    private static Object convertAttrValue(Class<?> clazz, String value)throws Exception{
        String type=clazz.getName();
        switch (type){
            case "java.lang.Long":
            case "long":
                return Long.valueOf(value);
            case "java.lang.Integer":
            case "int":
                return Integer.valueOf(value);
            case "java.lang.Double":
            case "double":
                return Double.valueOf(value);
            case "java.util.Date":
                String pattern=null;
                if (value.matches("\\d{4}-\\d{2}-\\d{2}")) {
                    pattern = "yyyy-MM-dd";
                } else if (value.matches("\\d{4}-\\d{2}-\\d{2} \\d{2}:\\d{2}:\\d{2}")) {
                    pattern = "yyyy-MM-dd HH:mm:ss";
                }
                if (pattern != null) {
                    try {
                        return new SimpleDateFormat(pattern).parse(value);
                    } catch (Exception e) {

                    }
                }
                return new Date();
//            case "java.lang.String":
//                return value;
        }
        return value;
    }
}

这些自动的级联配置的实例化处理操作,在以后进行项目的编写之中一定会使用到。

级联属性设置

  现在已经成功的实现级联的对象实例化处理,那么随后就需要去考虑级联的属性的设置了,在之前考虑级联对象实例化处理时,循环时都是少了一位的。

for (int i = 0,l=tmps.length-1; i <l ; i++) {//实例化
    //调用相应的getter方法
    tmpAttr=tmps[i];
    getMethod=getDeclaredMethod(currentObj.getClass(),"get",tmpAttr);
    tmpObject=getMethod.invoke(currentObj);
    if (tmpObject == null) {
        //该对象并没有实例化
        tmpField = currentObj.getClass().getDeclaredField(tmpAttr);//获取属性类型
        tmpMethod = getDeclaredMethod(currentObj.getClass(), "set", tmpAttr, tmpField.getType());
        Object newObj = tmpField.getType().getConstructor().newInstance();
        tmpMethod.invoke(currentObj, newObj);
        currentObj = newObj;
    }else{
        currentObj=tmpObject;
    }
}

当此时代码循环处理完成之后,currentObj表示的就是可以进行setter方法调用的对象了,并且理论上该对象一定不可能为null,随后就可以按照我们之前的方式利用对象进行setter方法调用。
范例:实现对象的级联属性设置

public static void setValue(Object obj,String value){
    String[] results=value.split("\\|"), attrs,tmps;//按照"|"进行每一组属性的拆分
    Field field,tmpField;
    Method setMethod,getMethod,tmpMethod;
    Object val,currentObj,tmpObject;
    String attr,tmpAttr;
    for (String result : results) {
        attrs=result.split(":");
        attr=attrs[0];
        try {
            if(attr.contains(".")){
                tmps=attr.split("\\.");
                 currentObj=obj;
                //最后一位肯定是指定类中的属性名称,所以不在本次实例化处理的范畴之内
                for (int i = 0,l=tmps.length-1; i <l ; i++) {//实例化
                    //调用相应的getter方法
                    tmpAttr=tmps[i];
                    getMethod=getDeclaredMethod(currentObj.getClass(),"get",tmpAttr);
                    tmpObject=getMethod.invoke(currentObj);
                    if (tmpObject == null) {
                        //该对象并没有实例化
                        tmpField = currentObj.getClass().getDeclaredField(tmpAttr);//获取属性类型
                        tmpMethod = getDeclaredMethod(currentObj.getClass(), "set", tmpAttr, tmpField.getType());
                        Object newObj = tmpField.getType().getConstructor().newInstance();
                        tmpMethod.invoke(currentObj, newObj);
                        currentObj = newObj;
                    }else{
                        currentObj=tmpObject;
                    }
                }
                tmpAttr=tmps[tmps.length-1];
                field=currentObj.getClass().getDeclaredField(tmpAttr);//获取成员
                setMethod=getDeclaredMethod(currentObj.getClass(),"set",tmpAttr,field.getType());
                val= convertAttrValue(field.getType(),attrs[1]);
                setMethod.invoke(currentObj,val);
            }else{
                field=obj.getClass().getDeclaredField(attr);
                setMethod=getDeclaredMethod(obj.getClass(),"set",attr,field.getType());
                val= convertAttrValue(field.getType(),attrs[1]);
                setMethod.invoke(obj,val);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

在以后的开发中简单Java类的赋值处理将不再重复调用setter操作完成,而这种处理形式是在正规开发中普遍采用的方式。




原文地址:访问原文地址
快照地址: 访问文章快照