反射与简单java类
传统属性赋值弊端
简单java类主要是由属性所组成,并且提供有setter和getter处理方法,同时简单Java类最大的特征是通过对象保存相应的类属性内容。但是使用传统的简单Java类开发,也会面临非常麻烦的困难。
传统Java类操作
class Emp{
private String ename;
private String job;
public String getEname() {
return ename;
}
public void setEname(String ename) {
this.ename = ename;
}
public String getJob() {
return job;
}
public void setJob(String job) {
this.job = job;
}
}
按照传统做法首先实例化Emp对象,通过Emp对象进行属性的设置和获取。再整个进行Emp对象实例化并设置数据的操作过程中设置数据的部分是最麻烦的,假设Emp中有50个属性,那么对于整个程序而言将出现一堆setter、getter的调用。或者进一步来讲实际开发中简单Java类个数非常之多,那么如果所有简单Java类都牵扯到属性赋值的时候,这种情况下代码编写重复性将非常高。
按照传统的直观的编程方式,会带来==大量的重复操作。==要想解决对象的重复处理操作,唯一的解决方案就是反射机制,反射机制最大的特点使可以根据其自身的特点(Object类直接操作、可以直接操作属性或方法)实现相同功能类的重复操作的抽象处理。
属性自动赋值实现思路
经过分析之后已经确认了当前简单Java类操作的问题所在,对于开发者而言需要考虑一个方法实现属性内容的自动设置,强烈建议使用字符串类型描述对应的类型。
- 进行程序开发时,String字符串可以描述的内容有很多,且可以由开发者自行定义字符串的结构,下面就采用“属性:内容|属性:内容”的形式来为简单Java类中的属性进行初始化。
- 类设计基本结构:应该有一个专门的ClasssInstanceFactory类负责所有的反射处理,即:接收反射对象与要设置的反射内容同时可获取指定类的实例化对象。
- 设计的基本结构
单级属性赋值
对于此时简单Java类里面发现所给出的数据类型都没有其他的引用关联,只是描述emp本类的对象,所以这样的设置成为单级设置出了,所以要处理两件事情:
- 需要通过反射进行指定类对象的实例化处理;
- 进行内容的设置(Field属性类型、方法名称、要设置的内容);
- 定义StringUtils实现首字符大写功能
class StringUtils{
public static String initcap(String str) {
if(str == null || "".equals(str)) {
return str;
}
if(str.length() ==1) {
return str.toUpperCase();
} else {
return str.substring(0,1).toUpperCase() + str.substring(1);
}
}
}
- 定义BeanUtils工具类,主要实现属性设置
class BeanUtils{ //进行Bean处理类
private BeanUtils() {}
/**
* 实现指定对象的属性设置
* @param obj 要进行反射操作的实例化对象
* @param value 包含指定内容符合格式的字符串
*/
public static void setValue(Object obj, String value){
String results[] = value.split("\\|");
for(int x = 0; x<results.length; x++) {
String attval[] = results[x].split(":"); //获取属性名称(attval[0])和内容(attval[1])
try {
Field field = obj.getClass().getDeclaredField(attval[0]);
Method setMethod = obj.getClass().getDeclaredMethod("set"+StringUtils.initcap(attval[0]), field.getType());
setMethod.invoke(obj, attval[1]); //调用setter方法设置内容
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
- ClassInstanceFactory负责实例化对象并且调用BeanUtils类实现属性内容的设置。
class ClassInstanceFactory{
private ClassInstanceFactory() {}
/**
* 实例化对象的创建方法,该对象可以根据传入字符串结果“属性:内容|属性:内容”
* @param <T>
* @param clazz 要进行反射实例化的Class类对象,有Class就可以反射实例化对象
* @param value 要设置给对象的属性内容
* @since 2021/3/8
* @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;
}
}
}
- 主类
public class test {
public static void main(String[] args) throws Exception {
String value = "ename:zky|job:Clerk";
Emp empObj = ClassInstanceFactory.create(Emp.class, value);
System.out.println(empObj.getEname() +"-"+ empObj.getJob());
}
}
即使现在属性再多,也可以轻松实现setter的调用(类对象实例化处理)
设置多种数据类型
当前所给定数据类型只是String,但实际开发中面对简单Java类中属性类型一般可选为:long(Long),int(Integer),double(Double),String,Date(日期,日期时间)。所以这个时候对于当前程序代码需要作出修改,要求实现各种数据类型配置。
既然要求可以实现不同类型的内容设置,并且BeanUtils类主要是完成属性赋值处理的,那么可以在这个类中追加一些处理方法。
修改属性设置类
class BeanUtils{ //进行Bean处理类
private BeanUtils() {}
/**
* 实现指定对象的属性设置
* @param obj 要进行反射操作的实例化对象
* @param value 包含指定内容符合格式的字符串
*/
public static void setValue(Object obj, String value){
String results[] = value.split("\\|");
for(int x = 0; x<results.length; x++) {
String attval[] = results[x].split(":"); //获取属性名称(attval[0])和内容(attval[1])
try {
Field field = obj.getClass().getDeclaredField(attval[0]);
Method setMethod = obj.getClass().getDeclaredMethod("set"+StringUtils.initcap(attval[0]), field.getType());
Object convertval = BeanUtils.convertAttributeValue(field.getType().getName(), attval[1]);
setMethod.invoke(obj, convertval); //调用setter方法设置内容
} catch (Exception e) {
// e.printStackTrace();
}
}
}
/**
* 实现属性类型转换处理
* @param type 属性类型,通过Field获取
* @param value 属性内容,传入都为字符串,将其变为指定类型
* @return 转换后的数据
*/
private static Object convertAttributeValue(String type,String value) {
if("long".equals(type) || "java.lang.Long".equals(type)) { //长整型
return Long.parseLong(value);
} else if("int".equals(type) || "java.lang.int".equals(type)) {
return Integer.parseInt(value);
} else if("double".equals(type) || "java.lang.double".equals(type)) {
return Double.parseDouble(value);
} else if("java.util.Date".equals(type)) {
SimpleDateFormat sdf = null;
if(value.matches("\\d{4}-\\d{2}-\\d{2}")) {
sdf = new SimpleDateFormat("yyyy-MM-dd");
} else if(value.matches("\\\\d{4}-\\\\d{2}-\\\\d{2} \\d{2}:\\d{2}:\\d{2}")) {
sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
} else {
return new Date();
}
try {
return sdf.parse(value);
} catch (ParseException e) {
return new Date();
}
} else {
return value;
}
}
}
此时只是列举出了常用数据类型,如果想要作为产品推广则必须考虑所有可能出现的数据类型,同时所有可能出现的日期格式都需要考虑。
级联对象实例化
如给定的类对象之中存在有其他的引用的级联关系的情况下,称为多级设置。例如:一个雇员属于一个部门,一个部门属于一个公司。这个时候对于简单Java类的基本关系定义如下。
class Company{
private String name;
private Date createdate;
}
class Dept{
private String dname;
private String loc;
private Company company;
}
class Emp{
private long empno;
private String ename;
private String job;
private double salary;
private Dept dept;
}
如果要通过Emp进行操作,则应该使用“.”作为级联关系的处理:
- dept.dname:财务部 | Emp类实例化对象.getDept().setName(“财务部”)
- dept.company.name:MLDN | Emp类实例化对象.getDept().getCompany().setName(“MLDN”)
考虑到代码的间接性,所以应该考虑可以通过级联的配置自动实现类中属性的实例化。
现在的属性存在多级的关系,那么对于多级的关系就必须与单级的配置区分开。
仔细看
/**
* 实现指定对象的属性设置
* @param obj 要进行反射操作的实例化对象
* @param value 包含指定内容符合格式的字符串
*/
public static void setValue(Object obj, String value){
String results[] = value.split("\\|");
for(int x = 0; x<results.length; x++) {
String attval[] = results[x].split(":"); //获取属性名称(attval[0])和内容(attval[1])
Object currentObject = obj;
try {
if(attval[0].contains(".")) { //多级配置
String temp[] = attval[0].split("\\.");
// System.out.println(Arrays.toString(temp));
//最后一位肯定是属性名称,所以不再本次实例化处理的范畴之内
for(int y=0; y<temp.length - 1; y++) {
//调用相应的getter方法,如果getter返回null表示该对象未实例化
Method getMethod = obj.getClass().getDeclaredMethod("get"+ StringUtils.initcap(temp[y]));
Object tempObject = getMethod.invoke(obj);
if(tempObject == null) { // 该对象并未实例化
Field field = currentObject.getClass().getDeclaredField(temp[y]); //获取属性类型
Method method = currentObject.getClass().getDeclaredMethod("set"+ StringUtils.initcap(temp[y]), field.getType());
Object newObject = field.getType().getDeclaredConstructor().newInstance();
currentObject = method.invoke(currentObject, newObject);
currentObject = newObject;
} else {
currentObject = tempObject;
}
System.out.println(temp[y] + "--" + currentObject);
}
} else {
Field field = obj.getClass().getDeclaredField(attval[0]);
Method setMethod = obj.getClass().getDeclaredMethod("set"+StringUtils.initcap(attval[0]), field.getType());
Object convertval = BeanUtils.convertAttributeValue(field.getType().getName(), attval[1]);
setMethod.invoke(obj, convertval); //调用setter方法设置内容
}
} catch (Exception e) {
// e.printStackTrace();
}
}
}
这种自动的级联配置的实例化处理操作,在以后的项目编写中一定会使用到。
级联属性赋值
考虑级联属性设置,在之前考虑级联对象实例化处理的时候会发现循环的时候都是少了一位的。
代码循环处理完成后,currentObject表示的就是可以进行setter方法调用的对象,理论上该对象一定不可能为空,随后就可以按照之前的方式利用对象进行setter方法的调用。
//属性内容设置
Field field = currentObject.getClass().getDeclaredField(temp[temp.length - 1]);
Method setMethod = currentObject.getClass().getDeclaredMethod("set"+StringUtils.initcap(temp[temp.length-1]), field.getType());
Object convertval = BeanUtils.convertAttributeValue(field.getType().getName(), attval[1]);
setMethod.invoke(obj, convertval); //调用setter方法设置内容
在以后开发中简单java类的赋值处理将不在重复调用setter操作完成,这种处理形式是在正规开发中普遍采用的形式。