有没有Java标准类实现了Iterable接口但没有实现Collection接口?

24
我有一个难题,让我思考是否有任何标准的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<? extends Integer>类型并不能消除该行的未经检查的警告。此外,我无法将方法签名更改为接受Collection而不是Iterable,因为那样就不再覆盖超级方法,并且在需要时不会被调用。
似乎没有办法解决这个强制转换或复制的问题:其他问题已经在这里和其他地方提出了,并且它似乎深深植根于Java的泛型和类型擦除系统中。但是我想问一下是否有任何可以实现Iterable<T>但不实现Collection<T>的类?我已经浏览了Iterable JavaDoc,并且我期望传递给我的接口的所有内容实际上都是集合。我想使用一个现成的类,因为它更有可能作为参数传递,并且可以使单元测试更有价值。
我确定我编写的复制或转移位适用于我在项目中使用的类型,因为我正在编写一些单元测试。但是,我想为一些输入编写一个单元测试,这些输入是可迭代的但不是集合,到目前为止,我只能想到实现一个虚拟测试类来进行测试。

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


6
еӣһзӯ”дҪ ж Үйўҳдёӯзҡ„й—®йўҳпјҡжҳҜзҡ„пјҢжҜ”еҰӮServiceLoaderгҖӮ - aioobe
1
做一个复制(即创建一个新的集合)有什么问题吗?你只会复制引用,而不是创建新对象。 - VGR
没有什么严重的问题,但我正在寻找一种避免进行任何分配的方法。正如名称所示,Lists.newArrayList 可以分配一个新数组,潜在地 log(n) 次,因为它在分配支持数组之前不会得到大小。(尽管再次查看 Guava 代码,我意识到我的 instanceof 然后转换是多余的。) - Patrick M
1
请注意,在调用此方法后,调用者仍然可以更新 keys。如果这会破坏您的假设,则在所有情况下都应优先考虑复制。 - Brandon
6个回答

13

虽然没有符合您需要并对测试代码读者直观的类,但您可以轻松创建自己的匿名类,以便更容易理解:

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.*/ }
            };
        }
    };
}

演示。

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


11

回答标题的问题:

是否有任何Java标准类实现了 Iterable 但未实现 Collection ?

源文本:

是否存在实现了 Iterable<T> 的类,而这些类不实现 Collection<T>

答案:

是的

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

任何列出了实现该接口的 Java 标准类的部分,都将列出实现 Iterable 接口的 Java 标准类。其中许多类都不实现 Collection


10

虽然有点笨拙,但我认为这段代码

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

这个设计是完全可靠的。接口 Collection<T> 扩展自 Iterable<T> 并且不允许使用2个不同的类型参数实现相同的接口,所以一个类不能同时实现 Collection<String>Iterable<Integer>

Integer 是final的,因此 Iterable<? extends Integer>Iterable<Integer> 之间的区别在很大程度上只是学术上的差异。

综合考虑最后两段,可以证明如果某个东西既是 Iterable<? extends Integer> 又是 Collection,那么它必须是 Collection<Integer>。因此,您的代码是安全的。编译器无法确定这一点,所以您可以通过编写以下内容来禁止警告:

@SuppressWarnings("unchecked")

在代码上方添加注释来解释代码为何是安全的。

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

编辑

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


我明白这种方法可能被证明是有效的,但这些都是不好的做法。特别是,@SuppressWarnings 假定所有未来维护此方法的程序员都不会犯错误。 - VGR
1
@VGR,这就是为什么我说要包含注释来解释为什么可以这样做。@SuppressWarnings("unchecked")在JDK中被广泛使用,它用于当您可以证明某些内容是安全的,但编译器无法证明时 - 正是这种情况。还有哪些不良实践? - Paul Boddington
未经检查的强制类型转换。从逻辑上讲,它只是“完全正确”的,但当其他人以某种方式修改代码时,这可能是第一件会出问题的事情。我认为这就像说“我不需要安全带,因为我是个好司机。” - VGR
我和@PaulBoddington持相同观点。该语言缺乏必要的工具,无法在100%的情况下避免未经检查的转换。你可以推动它们; 有时你可以将它们推入JRE,但你无法避免它们。 - bmargulies
1
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - Patrick M
显示剩余7条评论

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

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

1
我不明白其中一些是如何代表“糟糕的设计”的,您能进一步解释一下吗? - Octavia Togami
1
@ıɯɐƃoʇǝızuǝʞ - “arguably” :) 对我来说,for(x: sqlEx) 很糟糕,因为其含义不是立即清晰的。将其设计为 for(x: sqlEx.causes()) 会更好。 - ZhongYu
除了这些类的实用性之外,为测试代码重复使用一个命名不太直观的东西是一个糟糕的设计。对于列举选择,点赞加一。 - Patrick M

1
阅读了优秀的答案和提供的文档后,我在一些其他类中查找,并发现了看起来是胜利者的东西,无论是对于测试代码的简单性还是对于一个直接的问题标题。Java的主要 ArrayList 实现包含了这个宝石:
public Iterator<E> iterator() {
    return new Itr();
}

在这里,Itr是一个私有内部类,具有高度优化的自定义实现Iterator<E>。不幸的是,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());
  }
}

1

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

如果您使用的是Java 8,则已经为Iterable添加了(即给出了一个default方法)spliterator()(请注意其Implementation Note),这使您可以与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 实现的参数都会经过不必要的流式收集操作。如果使用标准化的仅限JDK方法的需求超过了潜在的性能损失,则可能应该使用它,与您原来的强制转换或基于Guava的方法相比,这可能是无关紧要的,因为您已经在使用Guava的 CacheLoader

为了测试这一点,请考虑以下片段和示例输出:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接