操纵jsom、xml、java bean等对象时你可能最先想到访问者模式。但是使用访问者模式很难从调用代码控制回调。比如,不能有条件的从所有回调的子分支和叶子节点跳过某个分支。解决这个问题就可以使用iterator模式遍历整个对象,生成易于开发者阅读和调试的字符串。该迭代器具备一定的通用性,我在使用xpath查找java对象和在stackhunter中记录异常的工具中都用到了它。
API
本文主要介绍的两个类:StringGenerator和ObjectIterator。
字符串生成器
StringGenerator工具类将对象转化为字符串,使对象可读性更好。可以用它来实现类的toString方法或者把对象的字符串表达作为日志调试代码:
package com.stackhunter.util.tostring.example;
import com.stackhunter.example.employee.Department;
import com.stackhunter.example.employee.Employee;
import com.stackhunter.example.employee.Manager;
import com.stackhunter.example.people.Person;
import com.stackhunter.util.tostring.StringGenerator;
public class StringGeneratorExample {
public static void main(String[] args) {
Department department = new Department(5775, "Sales")
.setEmployees(
new Employee(111, "Bill", "Gates"),
new Employee(222, "Howard", "Schultz"),
new Manager(333, "Jeff", "Bezos", 75000));
System.out.println(StringGenerator.generate(department));
System.out.println(StringGenerator.generate(new int[] { 111, 222, 333 }));
System.out.println(StringGenerator.generate(true));
}
}StringGenerator.generate()将department,数组和boolean值进行格式化输出。
com.stackhunter.example.employee.Department@129719f4
deptId = 5775
employeeList = java.util.ArrayList@7037717a
employeeList[0] = com.stackhunter.example.employee.Employee@17a323c0
firstName = Bill
id = 111
lastName = Gates
employeeList[1] = com.stackhunter.example.employee.Employee@57801e5f
firstName = Howard
id = 222
lastName = Schultz
employeeList[2] = com.stackhunter.example.employee.Manager@1c4a1bda
budget = 75000.0
firstName = Jeff
id = 333
lastName = Bezos
name = Sales
[I@39df3255
object[0] = 111
object[1] = 222
object[2] = 333
true对象迭代器
ObjectIterator使用迭代器模式遍历对象的属性,以键值对形式保存。对象中的Java bean、集合、数组及map都要进行迭代。ObjectIterator也会考虑到对象之间循环引用的处理。
package com.stackhunter.util.tostring.example;
import com.stackhunter.example.employee.Department;
import com.stackhunter.example.employee.Employee;
import com.stackhunter.example.employee.Manager;
import com.stackhunter.util.objectiterator.ObjectIterator;
public class ObjectIteratorExample {
public static void main(String[] args) {
Department department = new Department(5775, "Sales")
.setEmployees(
new Employee(111, "Bill", "Gates"),
new Employee(222, "Howard", "Schultz"),
new Manager(333, "Jeff", "Bezos", 75000));
ObjectIterator iterator = new ObjectIterator("some department", department);
while (iterator.next()) {
System.out.println(iterator.getName() + "=" + iterator.getValueAsString());
}
}
}通过遍历整个对象生成键值对的集合。使用getValueAsString()方法而不是toString()格式化输出。对于原始类型、包装类型、字符串、日期和枚举使用原始的toString()实现。对于其他类型输出类名和hash值。
ObjectIterator.getDepth()会增加缩进,输出更易读。调用next()之前使用nextParent()缩短当前分支跳跃到下一属性。
some department=com.stackhunter.example.employee.Department@780324ff deptId=5775 employeeList=java.util.ArrayList@6bd15108 employeeList[0]=com.stackhunter.example.employee.Employee@22a79c31 firstName=Bill ...
Java对象迭代器的具体实现
实现iterator模式的第一步是创建通用的迭代器接口:IObjectIterator。无论遍历的对象是Java bean、数组还是map都可以使用该接口。
public interface IObjectIterator {
boolean next();
String getName();
Object getValue();
}使用该接口可以按照单一顺序依次获取当前属性的name和value。
实现了IObjectIterator的类用来处理某一种类型的对象。大多数类调用getName()返回名称前缀。ArrayIterator使用了元素的索引:return name + "[" + nextIndex + "]";。

属性迭代器
PropertyIterator可能是最重要的迭代类。它使用Java bean introspection读取对象属性,将它们转化为键值对序列。
public class PropertyIterator implements IObjectIterator {
private final Object object;
private final PropertyDescriptor[] properties;
private int nextIndex = -1;
private PropertyDescriptor currentProperty;
public PropertyIterator(Object object) {
this.object = object;
try {
BeanInfo beanInfo = Introspector.getBeanInfo(object.getClass());
properties = beanInfo.getPropertyDescriptors();
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
@Override
public boolean next() {
if (nextIndex + 1 >= properties.length) {
return false;
}
nextIndex++;
currentProperty = properties[nextIndex];
if (currentProperty.getReadMethod() == null || "class".equals(currentProperty.getName())) {
return next();
}
return true;
}
@Override
public String getName() {
if (currentProperty == null) {
return null;
}
return currentProperty.getName();
}
@Override
public Object getValue() {
try {
if (currentProperty == null) {
return null;
}
return currentProperty.getReadMethod().invoke(object);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}
}数组迭代器
ArrayIterator通过反射得到数组的长度,进而检索每个数据元素。ArrayIterator不关心从getValue()方法返回值的具体细节。它们一般情况下被传递给PropertyIterator。
public class ArrayIterator implements IObjectIterator {
private final String name;
private final Object array;
private final int length;
private int nextIndex = -1;
private Object currentElement;
public ArrayIterator(String name, Object array) {
this.name = name;
this.array = array;
this.length = Array.getLength(array);
}
@Override
public boolean next() {
if (nextIndex + 1 >= length) {
return false;
}
nextIndex++;
currentElement = Array.get(array, nextIndex);
return true;
}
@Override
public String getName() {
return name + "[" + nextIndex + "]";
}
@Override
public Object getValue() {
return currentElement;
}
}集合迭代器
CollectionIterator与ArrayIterator非常相似。使用java.lang.Iterable调用它的Iterable.iterator()方法初始化内部迭代器。
Map迭代器
MapIterator遍历java.util.Map的entry。它并不深入到每个entry的键值对,这个工作由MapEntryIterator类完成。
public class MapIterator implements IObjectIterator {
private final String name;
private Iterator<?> entryIterator;
private Map.Entry<?, ?> currentEntry;
private int nextIndex = -1;
public MapIterator(String name, Map<?, ?> map) {
this.name = name;
this.entryIterator = map.entrySet().iterator();
}
@Override
public boolean next() {
if (entryIterator.hasNext()) {
nextIndex++;
currentEntry = (Entry<?, ?>) entryIterator.next();
return true;
}
return false;
}
...
}Map Entry迭代器
MapEntryIterator处理java.util.Map的单个entry。它只返回两个值:entry的键和值。与ArrayIterator及其他的类似,如果是复杂类型的话,它的结果可能最终传递给PropertyIterator,作为Java bean处理。
根迭代器
RootIterator返回单个元素——初始节点。可以把它想成XML文件的根节点。目的是发起整个遍历过程。
整合
ObjectIterator类作为门面角色(Facade),包装了所有的遍历逻辑。它根据最后一次getValue()的返回值类型决定哪个IObjectIterator的子类需要实例化。当子迭代器在内部创建时它在栈中保存当前迭代器的状态。它也暴露了getChild()和getDepth()方法为调用者展示当前进度。
private IObjectIterator iteratorFor(Object object) {
try {
if (object == null) {
return null;
}
if (object.getClass().isArray()) {
return new ArrayIterator(name, object);
}
if (object instanceof Iterable) {
return new CollectionIterator(name, (Iterable<?>) object);
}
if (object instanceof Map) {
return new MapIterator(name, (Map<?, ?>) object);
}
if (object instanceof Map.Entry) {
return new MapEntryIterator(name, (Map.Entry<?, ?>) object);
}
if (isSingleValued(object)) {
return null;
}
return new PropertyIterator(object);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new RuntimeException(e.getMessage(), e);
}
}字符串生成器的实现
已经看到如何遍历对象中的所有属性。最后的工作就是让输出更加美观,以及增加一些限制条件(比如不能创建一个GB级大小的字符串)。
public static String generate(Object object) {
String s = "";
ObjectIterator iterator = new ObjectIterator("object", object);
...
while (iterator.next()) {
if (s.length() >= MAX_STRING_LENGTH) {
return s;
}
if (iterator.getChild() >= MAX_CHILDREN) {
iterator.nextParent();
continue;
}
String valueAsString = iterator.getValueAsString();
s += System.lineSeparator();
s += indent(iterator.getDepth()) + truncateString(iterator.getName());
if (valueAsString == null) {
s += " = null";
} else {
s += " = " + truncateString(valueAsString);
}
}
return s;
}代码第21行完成了格式化,增加了缩进和层次结构使显示更美观。
同时增加了一些限制条件:
第9行——限制字符串长度在16k内。
第13行——限制任何父节点下子节点数量小于64.
第21&25行——限制键和值长度在64个字符。
总结
本文介绍了如何使用迭代器模式遍历包含各种复杂属性的对象。关键是将每种类型的迭代委派给各自的类实现。可以在你自己的软件中使用这两个工具。
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号