如何使用接受参数的迭代器为每个实现?

问题描述

我正在实现一个自定义JarFile 类。其特点之一是可以通过给出起始路径将其内容的迭代限制为一个子集。

简化类示例:

public class JarFile Iterable<JarEntry>
{
   // Wraps java.util.jar.JarFile internally
   java.util.jar.JarFile jarFile;
   ..
   
   @Override
   public Iterator<JarEntry> iterator() {
      return new JarIterator(jarFile);
   }
   
   public Iterator<JarEntry> iterator(String startPath) {
      return new JarIterator(jarFile,startPath);
   }
}

例如,一个jar文件包含一些路径:

a/file1.class
b/file2.class
c/file3.class

迭代 jar 文件通常会提供其中包含的所有条目。但我的 JarIterator 还提供了一个带有 path 参数的构造函数,该参数将迭代仅限于一个子集及其子集。

例如,这将输出与上面相同的列表:

JarFile jf = new JarFile("Test.jar")

for (JarEntry je : jf)
   System.out.println(je.getName());

我可以像这样访问备用迭代器:

JarIterator it = jf.iterator("b/");
   
while (it.hasNext()) {
   JarEntry je = it.next();
   
   System.out.println(je.getName());
}

输出是这个子集条目:

b/file2.class

一切都已经正常工作,但我想利用 Java 的 for 关键字,但这仅适用于认的无参数构造函数

有没有办法让它与我的自定义迭代器一起工作而不必调用 hasNext()next()

解决方法

注意:我认为这是糟糕的 API 设计 - 见下文。

没有办法使用名为 iterator 的方法为这个库的用户提供一个很好的 API。您有几种选择,但最明显的是:

不要返回迭代器,返回一个可迭代的。

这与我下面的建议相吻合,即您的方法在任何情况下都是一个糟糕的设计,但如果您必须拥有它,那就是它的名字很糟糕。一旦您解决了名称,您就可以返回可迭代对象:

for (JarEntry je : jf.filterByPrefix("b/")) { ... }

filterByPrefix 方法会返回一个 Iterable<JarEntry>;这需要是一个具有 iterator() (no-args!) 方法的对象,该方法返回一个迭代器,该迭代器仅在规定的条目上进行迭代。这将是微不足道的;想象一下你有那个 iterator(String prefix) 方法:

public Iterable<JarFile> filterByPrefix(String prefix) {
    return () -> iterator(prefix);
}

关于为什么这听起来不像是好的 API 设计的一些内省:

jf.iterator("b/");

不确定这是不是很棒的设计;一旦你沿着这条路走下去,哪里是 iterator 方法,它让我只迭代未压缩大小不大于规定值的文件?当我们在做的时候,让我只迭代文件名具有素数长度的文件在哪里?一个荒谬的问题,但我想让你意识到预先定义过滤条件是次优的:你不知道人们可能想要过滤什么,所以现在你和你图书馆的用户进行了一场风格辩论:如果需要过滤,并且您通过此迭代器方法提供此过滤,是否应该始终使用它?但这是不一致的。现在,循环访问 jar 文件、过滤掉所有大于 1mb 的条目的代码与过滤掉不以 "foo" 开头的内容的代码完全不同。有两种方法来完成同一件事通常是坏消息。最好不要那样做。

如果有一个紧迫的性能参数,这是有道理的,但没有。您的 impl 不能跳过不以 "b/" 开头的内容,速度比:

    if (!je.getName().startsWith("b/")) continue;

可以,这只是您要保存的一行 Java 代码。

如果必须,至少将其命名为 iteratorByPrefix 或其他名称。人们应该如何猜测 iterator("b/") 会遍历所有以 "b/" 开头的 条目?为什么不是“迭代所有包含文本 b/ 的条目”,或者甚至不是所有文本内容以 b/ 开头或以 b/ 结尾的条目,或者与 b/ 相关联的 zip 标签?

如果您有迫切的性能需求或无法忍受单行过滤器 if,那么与其按前缀过滤,不如按功能过滤?

for (JarEntry je : jf.onlyByName(n -> n.startsWith("b/")) {}

当您使用它时,也许还可以创建一个 only 方法,它传入的不是名称而是整个 JarEntry 对象。参数的类型为 Predicate<String>Predicate<JarEntry>