欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  php教程

使用Iterator模式将对象转成String

程序员文章站 2022-05-08 20:57:40
...
操纵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 + "]";。

使用Iterator模式将对象转成String

属性迭代器

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个字符。

总结

本文介绍了如何使用迭代器模式遍历包含各种复杂属性的对象。关键是将每种类型的迭代委派给各自的类实现。可以在你自己的软件中使用这两个工具。

相关标签: Iterator