缘起 (Motivation/intent)
JavaScript中可以遍历对象中的属性,但Java却没有这样的语言支持。例如一个普通POJO对象UserBean
public class UserBean {
private int id;
private String name;
private Date birthdate;
// getters & setters
}
现在想遍历对象中每个属性,获得如下的效果
UserBean user = new UserBean(1234, "张三", "1982-12-13");
for(Object propertyValue : user) {
System.out.println(propertyValue);
}
1234
张三
1982-12-13
解决方案 (Solution)
使用Iterator模式。要使某个对象可以进行遍历循环操作,亦即可以适用于foreach,该对象必须实现Iterable接口。这个接口将会强制实现iterator()方法。
public class UserBean implements Iterable
实现Iterator的步骤如下
1. 在iterator()方法中,实例化我们自己的Iterator实现类,这里称之为MyObjectPropertyIterator
2. 要获取任何JavaBean对象的信息,既可以利用反射,也可以利用java.beans.Introspector这个工具类来获取一个BeanInfo对象
2.1.1 BeanInfo beanInfo = Introspector.getBeanInfo(user.getClass());
2.1.2 从BeanInfo对象中,我们可以获得一个PropertyDescriptor数组,其中就包含了bean对象中所有属性信息
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
2.1.3 然后用PropertyDescription的readMethod方法获得其getter和setter,最后调用invoke方法得到返回结果
for(PropertyDescriptor propertyDescriptor : propertyDescriptors)
Object propertyValue = propertyDescriptor.getReadMethod().invoke(targetObject);
System.out.println(propertyValue);
}
// 与我们的遍历对象属性无关没有实现的必要
@Override
public void remove() {}
如果使用反射,则代码如下:
2.2.1 Field[] fields = user.getClass().getDeclaredFields();
2.2.2 遍历Field[]数组
for(Field field : fields) {
field.setAccessible(true); // 这句使我们可以访问似有成员变量
Object property = field.get(user);
}
3. 利用步骤3的思路,创建我们的Iterator实现
public class PropertyIterator implements Iterator{
private int index;
private Object targetObject;
PropertyDescriptor[] propertyDescriptors;
// 通过构造器传入所要遍历的对象,即UserBean的对象
PropertyIterator(Object targetObject) {
this.targetObject = targetObject;
try {
BeanInfo beanInfo = Introspector.getBeanInfo(targetObject.getClass());
propertyDescriptors = beanInfo.getPropertyDescriptors();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public boolean hasNext() {
return this.index < this.propertyDescriptors.length;
}
@Override
public Object next() {
Object propertyValue = null;
try {
PropertyDescriptor propertyDescriptor = propertyDescriptors[index++];
propertyValue= propertyDescriptor.getReadMethod().invoke(targetObject);
} catch (Exception e) {
e.printStackTrace();
}
return propertyValue;
}
//如果用反射
public class PropertyIterator implements Iterable {
private final Object targetObject;
public PropertyIterator(final Object targetObject) {
this.targetObject = targetObject;
}
public Iterator iterator() {
return new PropertyIteratorImpl();
}
private class PropertyIteratorImpl implements Iterator{
int index;
Field[] fields;
PropertyIteratorImpl() {
try {
fields = targetObject.getClass().getDeclaredFields();
} catch (Exception e) {
e.printStackTrace();
}
}
@Override
public boolean hasNext() {
return this.index < this.fields.length;
}
@Override
public Object next() {
Object obj = null;
try {
Field field = fields[index++];
field.setAccessible(true);
obj = field.get(targetObject);
} catch (Exception e) {
e.printStackTrace();
}
return obj;
}
@Override
public void remove() {}
}
}
@Override
public Iterator iterator() {
return new MyPropertyIterator(this);
}
说明 (Note)
为了简明,没有说明Iterator设计模式的实现原理,关于这部分,秦参考有关设计模式的书籍,特别是GoF。或可参考java.util.LinkedList API的源代码。
评估 (Assessment)
对目标对象完全没有侵入性,将遍历功能与目标对象本身分离。上一个版本,让UserBean对象直接实现Itarable,虽然更直观,代码量也少,但通用型不强,有侵入性。如果不是自己创建的类,无法使用。这个版本将Iterator类的实现分离成单独一个类,使之更通用化。现在的版本已经不需要Bean对象实现Iterable接口,使得UserBean类更干净、纯粹,不牵涉和UserBeen业务无关的任何接口或抽象类,符合将变化点分离的OO设计思想。
另外,BeanInfo版本和反射版本有细微差别:BeanInfo的PropertyDescriptor通过getter读取类变量,而反射则直接读取似有成员变量。后者显得更暴力些。虽然从代码编写者的角度似乎更方便,但破坏了OO的封装和信息隐藏原则。
本篇笔记的目的是灵活使用Iterator模式。和大多数书本或网上教程不同,本篇利用Iterator模式实现了一个非Collection类的遍历。