是否有任何 Java 标准 类 实现 Iterable 而不实现 Collection?

Are there any Java standard classes that implement Iterable without implementing Collection?

我有一个难题,这让我思考是否有任何标准 java classes 实现了 Iterable<T> 而没有实现 Collection<T>。我正在实现一个接口,该接口要求我定义一个接受 Iterable<T> 的方法,但我用来支持此方法的对象需要一个 Collection<T>.

这让我做了一些感觉很糟糕的代码,在编译时会给出一些未经检查的警告。

public ImmutableMap<Integer, Optional<Site>> loadAll(
        Iterable<? extends Integer> keys
) throws Exception {
    Collection<Integer> _keys;
    if (keys instanceof Collection) {
        _keys = (Collection<Integer>) keys;
    } else {
        _keys = Lists.newArrayList(keys);
    }

    final List<Site> sitesById = siteDBDao.getSitesById(_keys);
    // snip: convert the list to a map

将生成的集合更改为使用更通用的 Collection<? extends Integer> 类型并不能消除该行的未检查警告。此外,我无法更改方法签名以接受 Collection 而不是 Iterable,因为这样它就不再覆盖超级方法,也不会在需要时被调用。

那里 doesn't seem to be a way around this cast-or-copy problem: other questions have been asked here an elsewhere and it seems deeply rooted in Java's generic and type erasure systems. But I'm asking instead if there ever are any classes that can implement Iterable<T> that don't also implement Collection<T>? I've taken a look through the Iterable JavaDoc 当然,我希望传递到我的界面的所有内容实际上都是一个集合。我想使用一个预先写好的 class 来代替,因为它似乎更有可能作为参数实际传递,并且会使单元测试更有价值。

由于我正在编写一些单元测试,我确信我编写的强制转换或复制位适用于我在我的项目中使用它的类型。但是我想为一些 可迭代但 不是 集合的输入编写单元测试,到目前为止我已经能够想出的是自己实施一个虚拟测试 class 实施。


出于好奇,我正在实现的方法是 Guava 的 CacheLoader<K, V>.loadAll(Iterable<? extends K> keys) and the backing method is a JDBI instantiated data-access object, which requires a collection to be used as the parameter type for the @BindIn 接口。我认为我认为这与问题无关是正确的,但以防万一有人想对我的问题进行横向思考。我知道我可以分叉 JDBI 项目并重写 @BindIn 注释以接受可迭代...

虽然没有 class 可以立即满足您的需求并且对您的测试代码的读者来说是直观的,但您可以轻松地创建自己的匿名 class 并且易于理解:

static Iterable<Integer> range(final int from, final int to) {
    return new Iterable<Integer>() {
        public Iterator<Integer> iterator() {
            return new Iterator<Integer>() {
                int current = from;
                public boolean hasNext() { return current < to; }
                public Integer next() {
                    if (!hasNext()) { throw new NoSuchElementException(); }
                    return current++;
                }
                public void remove() { /*Optional; not implemented.*/ }
            };
        }
    };
}

Demo.

此实现是匿名的,不实现 Collection<Integer>。另一方面,它产生一个非空的可枚举整数序列,您可以完全控制它。

根据标题回答问题:

Are there any Java standard classes that implement Iterable without implementing Collection?

来自文字:

If there ever are any classes that can implement Iterable<T> that don't also implement Collection<T>?

答案:

请参阅以下 javadoc 页面:https://docs.oracle.com/javase/8/docs/api/java/lang/class-use/Iterable.html

任何说 Classes in XXX that implement Iterable 的部分将列出 Java 标准 类 接口实现。其中许多没有实现 Collection.

Kludgy,是的,但我认为代码

Collection<Integer> _keys;
if (keys instanceof Collection) {
    _keys = (Collection<Integer>) keys;
} else {
    _keys = Lists.newArrayList(keys);
}

非常完美。接口 Collection<T> 扩展 Iterable<T> 并且不允许使用 2 个不同类型的参数实现相同的接口,因此 class 无法实现 Collection<String>Iterable<Integer>,例如。

class Integer 是最终的,所以 Iterable<? extends Integer>Iterable<Integer> 之间的区别主要是学术上的。

综上所述,最后两段证明如果某物既是 Iterable<? extends Integer> 又是 Collection,那么它一定是 Collection<Integer>。因此,您的代码保证是安全的。编译器无法确定这一点,因此您可以通过编写

来抑制警告
@SuppressWarnings("unchecked")

上面的声明。您还应该在注释中包含一条注释,以解释为什么代码是安全的。

至于是否有 classes 实现了 Iterable 但没有实现 Collection,正如其他人指出的那样,答案是肯定的。但是我认为您真正要问的是拥有两个接口是否有任何意义。还有很多人问过这个。通常当一个方法有一个 Collection 参数时(例如 addAll() 它可以而且可能应该是一个 Iterable.

编辑

@Andreas 在评论中指出 Iterable 仅在 Java 5 中引入,而 Collection 是在 Java 1.2 中引入的,并且大多数现有方法出于兼容性原因,采用 Collection 无法改装为采用 Iterable

在核心 API 中,只有 Iterable 而不是 Collection 的类型 --

interface java.nio.file.Path

interface java.nio.file.DirectoryStream
interface java.nio.file.SecureDirectoryStream

class java.util.ServiceLoader

class java.sql.SQLException (and subclasses)

可以说这些都是糟糕的设计。

的回答中所述,Iterable 的一种此类实现是 Java 7 中引入的用于文件系统遍历的新 Path class .

如果您碰巧在 Java 8,Iterable 已被改装(即给定一个 default 方法)spliterator() (pay attention to its Implementation Note), which lets you use it in conjunction with StreamSupport:

public static <T> Collection<T> convert(Iterable<T> iterable) {
    // using Collectors.toList() for illustration, 
    // there are other collectors available
    return StreamSupport.stream(iterable.spliterator(), false)
                        .collect(Collectors.toList());
}

这是以任何已经是 Collection 实现的参数都经过不必要的流和收集操作为代价的。与您最初的转换或基于 Guava 的方法相比,如果对标准化 JDK 方法的需求超过潜在的性能影响,您可能应该只使用它,这可能没有实际意义,因为您已经在使用 Guava 的 CacheLoader.

要对此进行测试,请考虑以下代码段和示例输出:

// Snippet
System.out.println(convert(Paths.get(System.getProperty("java.io.tmpdir"))));
// Sample output on Windows
[Users, MyUserName, AppData, Local, Temp]

在阅读了出色的答案和提供的文档后,我又浏览了一些 classes 并找到了看起来是赢家的东西,无论是在测试代码的直截了当还是直接的问题标题方面. Java 的主要 ArrayList 实现包含此 gem:

public Iterator<E> iterator() {
    return new Itr();
}

其中 Itr 是私有内部 class,具有高度优化的自定义实现 Iterator<E>。不幸的是,Iterator 本身并没有实现 Iterable,所以如果我想将它硬塞到我的辅助方法中以测试不执行转换的代码路径,我必须将它包装在我的拥有实现 Iterable(而不是 Collection)和 returns 和 Itr 的垃圾 class。这是一种无需自己编写迭代代码即可轻松将 collection 转换为 Iterable 的简便方法。

最后一点,我的最终版本的代码甚至没有自己进行转换,因为 Guava 的 Lists.newArrayList 所做的与我在问题中对运行时类型检测所做的几乎完全相同。

@GwtCompatible(serializable = true)
public static <E> ArrayList<E> More ...newArrayList(Iterable<? extends E> elements) {
  checkNotNull(elements); // for GWT
  // Let ArrayList's sizing logic work, if possible
  if (elements instanceof Collection) {
    @SuppressWarnings("unchecked")
    Collection<? extends E> collection = (Collection<? extends E>) elements;
    return new ArrayList<E>(collection);
  } else {
    return newArrayList(elements.iterator());
  }
}