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

夙愿已清 提交于 2019-12-23 06:48:24

问题


I have a conundrum that's caused me to ponder whether there are any standard java classes that implement Iterable<T> without also implementing Collection<T>. I'm implementing one interface that requires me to define a method that accepts an Iterable<T>, but the object I'm using to back this method requires a Collection<T>.

This has me doing some really kludgy feeling code that give some unchecked warnings when compiled.

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

Changing my resulting collection to use the more generified Collection<? extends Integer> type doesn't eliminate the unchecked warning for that line. Also, I can't change the method signature to accept a Collection instead of an Iterable because then it's no longer overriding the super method and won't get called when needed.

There 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 and certainly everything I expect to be passed to my interface will actually be a collection. I'd like to use an in-the-wild, pre-written class instead as that seems much more likely to actually be passed as a parameter and would make the unit test that much more valuable.

I'm certain the cast-or-copy bit I've written works with the types I'm using it for in my project due to some unit tests I'm writing. But I'd like to write a unit test for some input that is an iterable yet isn't a collection and so far all I've been able to come up with is implementing a dummy-test class implementation myself.


For the curious, the method I'm implementing is Guava's 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 interface. I think I'm correct in thinking this is tangental to the question, but just in case anyone wants to try lateral thinking on my problem. I'm aware I could just fork the JDBI project and rewrite the @BindIn annotation to accept an iterable...


回答1:


Although there is no class that would immediately suit your needs and be intuitive to the readers of your test code, you can easily create your own anonymous class that is easy to understand:

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.

This implementation is anonymous, and it does not implement Collection<Integer>. On the other hand, it produces a non-empty enumerable sequence of integers, which you can fully control.




回答2:


To answer the question as per title:

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

From text:

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

Answer:

Yes

See the following javadoc page: https://docs.oracle.com/javase/8/docs/api/java/lang/class-use/Iterable.html

Any section that says Classes in XXX that implement Iterable, will list Java standard classes implementing the interface. Many of those don't implement Collection.




回答3:


Kludgy, yes, but I think the code

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

is perfectly sound. The interface Collection<T> extends Iterable<T> and you are not allowed to implement the same interface with 2 different type parameters, so there is no way a class could implement Collection<String> and Iterable<Integer>, for example.

The class Integer is final, so the difference between Iterable<? extends Integer> and Iterable<Integer> is largely academic.

Taken together, the last 2 paragraphs prove that if something is both an Iterable<? extends Integer> and a Collection, it must be a Collection<Integer>. Therefore your code is guaranteed to be safe. The compiler can't be sure of this so you can suppress the warning by writing

@SuppressWarnings("unchecked")

above the statement. You should also include a comment by the annotation to explain why the code is safe.

As for the question of whether there are any classes that implement Iterable but not Collection, as others have pointed out the answer is yes. However I think what you are really asking is whether there is any point in having two interfaces. Many others have asked this. Often when a method has a Collection argument (e.g. addAll() it could, and probably should, be an Iterable.

Edit

@Andreas has pointed out in the comments that Iterable was only introduced in Java 5, whereas Collection was introduced in Java 1.2, and most existing methods taking a Collection could not be retrofitted to take an Iterable for compatibility reasons.




回答4:


In core APIs, the only types that are Iterable but not 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)

Arguably these are all bad designs.




回答5:


As mentioned in @bayou.io's answer, one such implementation for Iterable is the new Path class for filesystem traversal introduced in Java 7.

If you happen to be on Java 8, Iterable has been retrofitted with (i.e. given a default method) 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());
}

This comes at the slight expense that any argument which is already a Collection implementation goes through an unnecessary stream-and-collect operation. You probably should only use it if the desire for a standardized JDK-only approach outweighs the potential performance hit, compared to your original casting or Guava-based methods, which is likely moot since you're already using Guava's CacheLoader.

To test this out, consider this snippet and sample output:

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



回答6:


After reading the excellent answers and provided docs, I poked around in a few more classes and found what looks to be the winner, both in terms of straightforwardness for test code and for a direct question title. Java's main ArrayList implementation contains this gem:

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

Where Itr is a private inner class with a highly optimized, customized implementation of Iterator<E>. Unfortunately, Iterator doesn't itself implement Iterable, so if I want to shoe horn it into my helper method for testing the code path that doesn't do the cast, I have to wrap it in my own junk class that implements Iterable (and not Collection) and returns the Itr. This is a handy way to easily to turn a collection into an Iterable without having to write the iteration code yourself.

On a final note, my final version of the code doesn't even do the cast itself, because Guava's Lists.newArrayList does pretty much exactly what I was doing with the runtime type detection in the question.

@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());
  }
}


来源:https://stackoverflow.com/questions/32570534/are-there-any-java-standard-classes-that-implement-iterable-without-implementing

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!