Guava 库为 Java 开发者提供了一系列强大的迭代器增强工具,它们简化了复杂迭代模式的实现。本文将深入探讨 Guava 的 PeekingIterator、AbstractIterator 和 AbstractSequentialIterator。
1. PeekingIterator:洞察先机
标准的 Iterator 接口仅提供 hasNext() 和 next() 方法,这在某些场景下显得力不从心。当需要“预读”下一个元素以做出决策,但又不想立即消耗它时,PeekingIterator 应运而生。
核心功能:
Iterators.peekingIterator(Iterator)方法用于将现有的Iterator包装成一个PeekingIterator。- 其核心方法是
peek(),允许在不推进迭代器的情况下,查看下一个调用next()时将返回的元素。
它带来的便利:
PeekingIterator 的 peek() 方法能够支持更灵活的迭代逻辑,尤其是在需要根据当前元素和“下一个”元素关系进行判断的场景中。它允许在一次迭代过程中完成复杂的逻辑判断和数据处理,避免了传统方法中可能出现的冗余 next() 调用或额外的状态变量管理。
示例:消除连续重复元素
1public class PeekingIteratorExample { 2 public static void main(String[] args) { 3 List<Integer> source = Arrays.asList(1, 1, 2, 3, 3, 3, 4, 5, 5); 4 List<Integer> result = Lists.newArrayList(); 5 6 PeekingIterator<Integer> iter = Iterators.peekingIterator(source.iterator()); 7 8 while (iter.hasNext()) { 9 Integer current = iter.next(); // 获取当前元素 10 result.add(current); // 将当前元素添加到结果 11 12 // 利用 peek() 提前判断,如果下一个元素与当前元素重复,则跳过。 13 while (iter.hasNext() && iter.peek().equals(current)) { 14 iter.next(); // 跳过这个重复元素 15 } 16 } 17 System.out.println("Original: " + source); 18 System.out.println("Deduplicated: " + result); // Output: [1, 2, 3, 4, 5] 19 } 20} 21
在此示例中,我们仅对 source 列表进行了一次逻辑遍历。PeekingIterator 使得我们能够在处理 current 元素的同时,预判并跳过所有后续的重复项,而无需将它们存储到任何中间结构或进行后续的去重处理。这简化了去重逻辑的实现。
注意事项: 由 Iterators.peekingIterator 返回的 PeekingIterator 不支持在调用 peek() 之后执行 remove() 操作。
2. AbstractIterator:一个方法定义迭代器
当需要实现一个自定义的 Iterator,例如对数据进行过滤、转换或从复杂数据源中提取时,手动管理 hasNext() 和 next() 的内部状态会引入大量样板代码。AbstractIterator 极大地简化了这一过程。
核心功能:
- 开发者只需实现一个方法:
computeNext()。此方法负责计算并返回序列中的下一个值。 - 当迭代序列完成时,
computeNext()应返回endOfData()以标记迭代结束。
它带来的便利:
AbstractIterator 采用惰性求值(lazy evaluation)机制。它确保 computeNext() 方法仅在真正需要下一个元素(即 next() 被调用时)才会被执行。这使得迭代器能够按需生成元素,避免了在迭代器未被完全消费时进行不必要的计算。同时,Guava 负责处理 hasNext() 和 next() 之间的状态同步,简化了自定义迭代器的实现。
示例:惰性过滤 Null 值
1public class AbstractIteratorExample { 2 3 public static Iterator<String> skipNulls(final Iterator<String> in) { 4 return new AbstractIterator<String>() { 5 @Override 6 protected String computeNext() { 7 while (in.hasNext()) { 8 String s = in.next(); 9 if (s != null) { 10 return s; // 找到非 null 元素,返回 11 } 12 } 13 return endOfData(); // 内部迭代器耗尽,返回 endOfData() 14 } 15 }; 16 } 17 18 public static void main(String[] args) { 19 List<String> source = Arrays.asList("hello", null, "world", null, "guava", "java"); 20 Iterator<String> filteredIterator = skipNulls(source.iterator()); 21 22 System.out.println("Filtered elements (only consuming first 3):"); 23 int count = 0; 24 while (filteredIterator.hasNext() && count < 3) { // 假设我们只消费前3个元素 25 System.out.println(filteredIterator.next()); 26 count++; 27 } 28 // Output: 29 // hello 30 // world 31 // guava 32 } 33} 34
在此示例中,即使 source 列表中有更多元素,我们只消费了前 3 个非空字符串。AbstractIterator 确保了 in.next() 和 if (s != null) 这样的操作仅执行了刚好足以找到这 3 个元素所需的最小次数。这种按需计算的模式,使得迭代器只在需要时才处理数据。
限制: AbstractIterator 继承自 UnmodifiableIterator,这意味着它禁止实现 remove() 方法。如果您的迭代器必须支持 remove(),则不应继承 AbstractIterator。
``
3. AbstractSequentialIterator
对于那些下一个值很容易根据前一个值计算出来的迭代器,AbstractSequentialIterator 提供了一种表达迭代的替代方式。
核心功能:
- 开发者实现的方法是
computeNext(T previous),它接受序列中的前一个值作为参数。 - 必须在构造函数中提供一个初始值(或者如果迭代器应立即终止,则传入
null)。 computeNext()方法约定,返回null意味着迭代结束。
它带来的便利:
AbstractSequentialIterator 适用于那些下一个值可以根据前一个值计算出来的序列。它通过**按需生成(on-demand generation)**机制,仅在 next() 被调用时才计算下一个元素。这使得它非常适合处理可能非常长甚至无限的序列,因为它避免了预先计算和存储整个序列。
示例:惰性计算 2 的幂次
1public class AbstractSequentialIteratorExample { 2 public static void main(String[] args) { 3 // 从1开始生成2的幂次方,直到达到 1 << 30 4 Iterator<Integer> powersOfTwo = new AbstractSequentialIterator<Integer>(1) { 5 @Override 6 protected Integer computeNext(Integer previous) { 7 // 如果 previous 已经达到最大值,则返回 null 结束迭代 8 return (previous == 1 << 30) ? null : previous * 2; 9 } 10 }; 11 12 System.out.println("Powers of two (only consuming first 7):"); 13 int count = 0; 14 while (powersOfTwo.hasNext() && count < 7) { // 假设我们只消费前7个元素 15 System.out.println(powersOfTwo.next()); 16 count++; 17 } 18 // Output: 1, 2, 4, 8, 16, 32, 64 19 } 20} 21
在此示例中,我们仅消费了序列的前 7 个元素。AbstractSequentialIterator 确保了只有这 7 次乘法操作被执行,并且在内存中只维护了当前 previous 元素的状态。这种惰性生成方式,避免了对整个序列进行预计算和存储的需要。
关键限制: 由于返回 null 标志着迭代的结束,因此 AbstractSequentialIterator 不能用于实现一个可以合法返回 null 元素的迭代器。
笔者遇到一个按需深拷贝的需求,简单描述为:已获得对象a,需要深拷贝若干份,同时需要标记序号,a已经实现deepCopy方法。实现如下:
1// 实现1 2A a = getA(); 3boolean first = true; 4for (var x: request.getXList()) { 5 // ... 6 if (a != null) { 7 if (first) { 8 x.setA(a); 9 first = false; 10 } else { 11 A newA = a.deepCopy(); 12 newA.seq++; 13 x.setA(newA); 14 } 15 } 16 // ... 17} 18 19// 实现2,修改引用a 20A a = getA(); 21for (var x: request.getXList()) { 22 // ... 23 if (a != null) { 24 x.setA(a); 25 a = a.deepCopy(); //会多复制一次 26 a.seq++; 27 } 28 // ... 29} 30 31// 新实现 32A a = getA(); 33// copy iter 34Iterator<A> aIter = new AbstractSequentialIterator<>(a) { 35 @Override 36 protected A computeNext(A prev) { 37 A result = prev.deepCopy(); 38 result.seq++; 39 return result; 40 } 41}; 42for (var x: request.getXList()) { 43 // ... 44 if (a != null) { 45 x.setA(aIter.next()); 46 } 47 // ... 48} 49
和原有实现相比,新实现抽取出了A的迭代逻辑,更好理解。
总结
Guava 的 PeekingIterator、AbstractIterator 和 AbstractSequentialIterator 为 Java 开发者提供了强大的工具,用于简化和增强迭代器的使用。它们通过以下方式提升代码的健壮性和可维护性:
- PeekingIterator: 允许在单次遍历中进行预判和复杂逻辑处理,减少了状态管理和冗余操作。
- AbstractIterator: 强制惰性求值,简化了自定义迭代器的实现,并按需处理数据。
- AbstractSequentialIterator: 实现按需生成序列,适用于处理长序列,避免了不必要的预计算和存储。
在处理数据流、构建数据管道或实现复杂业务逻辑时,合理利用这些迭代器,可以使代码更加清晰、灵活,并更好地管理资源。
《Guava 迭代器增强类介绍》 是转载文章,点击查看原文。