有没有实现 Iterable 而不实现 Collection 的 Java 标准类?

Posted

技术标签:

【中文标题】有没有实现 Iterable 而不实现 Collection 的 Java 标准类?【英文标题】:Are there any Java standard classes that implement Iterable without implementing Collection? 【发布时间】:2015-12-10 19:02:11 【问题描述】:

我有一个难题让我思考是否有任何标准的 java 类实现了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&lt;? extends Integer&gt; 类型并不能消除该行的未经检查的警告。此外,我无法将方法签名更改为接受Collection 而不是Iterable,因为这样它就不再覆盖超级方法并且在需要时不会被调用。

doesn't seem to be a way around 这个转换或复制问题:这里和其他地方已经提出了其他问题,它似乎深深植根于 Java 的泛型和类型擦除系统。但我要问的是,是否有任何类可以实现Iterable&lt;T&gt;,但也不能实现Collection&lt;T&gt;?我查看了Iterable JavaDoc,当然我希望传递给我的界面的所有内容实际上都是一个集合。我想改用一个狂野的、预先编写的类,因为这似乎更有可能实际作为参数传递,并使单元测试更有价值。

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


出于好奇,我正在实现的方法是 Guava 的 CacheLoader&lt;K, V&gt;.loadAll(Iterable&lt;? extends K&gt; keys),支持方法是 JDBI 实例化的数据访问对象,它需要一个集合用作 @BindIn 接口的参数类型。我认为我认为这与问题相切是正确的,但以防万一有人想尝试横向思考我的问题。我知道我可以只分叉 JDBI 项目并重写 @BindIn 注释以接受可迭代...

【问题讨论】:

回答标题中的问题:例如是ServiceLoader 复制有什么问题——也就是制作一个新的收藏?您只会复制引用,而不是创建新对象。 没什么大错,但我一直在寻找一种方法来避免进行任何分配。顾名思义,Lists.newArrayList 可以分配一个新数组,可能是log(n) 次,因为它在分配后备数组之前没有获得大小。 (虽然再看番石榴代码,才发现自己的instanceof then cast是多余的。) 请注意,调用者在调用此方法后仍然可以更新keys。如果这打破了您的假设,那么在所有情况下都可能需要一份副本。 【参考方案1】:

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

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&lt;Integer&gt;。另一方面,它产生一个非空的可枚举整数序列,你可以完全控制它。

【讨论】:

【参考方案2】:

按标题回答问题:

有没有实现Iterable而不实现Collection的Java标准类?

来自文字:

如果有任何类可以实现Iterable&lt;T&gt;,但又不能实现Collection&lt;T&gt;

答案:

是的

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

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

【讨论】:

【参考方案3】:

Kludgy,是的,但我认为代码

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

完美无缺。接口 Collection&lt;T&gt; 扩展了 Iterable&lt;T&gt; 并且不允许使用 2 个不同的类型参数实现同一个接口,因此例如,一个类无法实现 Collection&lt;String&gt;Iterable&lt;Integer&gt;

Integer 是final,所以Iterable&lt;? extends Integer&gt;Iterable&lt;Integer&gt; 之间的区别主要是学术性的。

综合来看,最后 2 段证明如果某事物既是 Iterable&lt;? extends Integer&gt; 又是 Collection,那么它一定是 Collection&lt;Integer&gt;。因此,您的代码保证是安全的。编译器无法确定这一点,因此您可以通过编写来抑制警告

@SuppressWarnings("unchecked")

在声明之上。您还应该在注释中添加注释以解释代码安全的原因。

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

编辑

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

【讨论】:

我知道这可以被证明是有效的,但这些都是不好的做法。特别是,@SuppressWarnings 假设所有维护此方法的未来编码人员永远不会出错。 @VGR 这就是为什么我说包括评论来解释为什么可以。 @SuppressWarnings("unchecked") 在整个 JDK 中使用,它适用于您可以证明某些东西是安全的,但编译器不能 - 正是这种情况。还有哪些不好的做法? 未经检查的演员表。它只是逻辑意义上的“完美声音”,当其他人介入以某种方式修改代码时,这可能是第一个被打破的事情。我会把它比作说“我不需要安全带,因为我是个好司机。” @VGR 正如我所说,标准库中有大量未经检查的强制转换。例如Optional 使用一个能够为Optional.empty() 返回相同的实例,无论您提供什么类型。另一种方法是每次都创建一个新对象。这类似于OP的问题。是的,您可以复制List,但我们不知道List 有多长,或者该方法是否应该改变参数。在我看来,我的回答中唯一不好的建议是我说过将@SuppressWarnings 应用于该方法(我将其更改为声明)。 每个人都可能有兴趣知道,我正在使用的 Guava 的助手 Lists.newArrayList 方法实际上与在封面下的未经检查的演员相同。【参考方案4】:

在核心 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)

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

【讨论】:

我看不出其中一些如何代表“糟糕的设计”,您能进一步解释一下吗? @ıɯɐƃoʇǝızuǝʞ - “可以说” :) 对我来说,for(x : sqlEx) 很糟糕,因为它的含义还不是很清楚。最好设计成for(x: sqlEx.causes()) 撇开这些类的有用性不谈,重用一些如此不直观地命名为测试代码的东西将是一个糟糕的设计。 +1 用于列举选择。【参考方案5】:

正如@bayou.io 的回答中提到的,Iterable 的一个这样的实现是 Java 7 中引入的用于文件系统遍历的新 Path 类。

如果您碰巧在 Java 8 上,Iterable 已被改造为(即给定一个 default 方法)spliterator()(注意它的实施说明),它可以让您与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

要对此进行测试,请考虑以下 sn-p 和示例输出:

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

【讨论】:

【参考方案6】:

在阅读了出色的答案并提供了文档后,我又浏览了一些课程,发现无论是在测试代码的直接性还是直接的问题标题方面,似乎都是赢家。 Java 的主要 ArrayList 实现包含这个 gem:

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

其中Itr 是一个私有内部类,具有Iterator&lt;E&gt; 的高度优化定制实现。不幸的是,Iterator 本身并没有实现Iterable,所以如果我想把它硬塞到我的辅助方法中来测试不进行强制转换的代码路径,我必须将它包装在我自己的垃圾类中实现Iterable(而不是Collection)并返回Itr。这是一种方便的方法,可以轻松地将集合转换为 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());
  

【讨论】:

以上是关于有没有实现 Iterable 而不实现 Collection 的 Java 标准类?的主要内容,如果未能解决你的问题,请参考以下文章

为啥Java中的String类没有实现Iterable?

LinkedList源码和并发问题分析

LinkedList源码和并发问题分析

为什么数组没有实现Iterable接口,但可以使用foreach语句遍历

如何创建只接受实现 Iterable 的元素的方法

Java里的实现了Iterable的类用iterator遍历完后怎么再次遍历?