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...

Community
  • 1
  • 1
Patrick M
  • 10,547
  • 9
  • 68
  • 101
  • 6
    To answer the question in your title: Yes [`ServiceLoader`](https://docs.oracle.com/javase/8/docs/api/java/util/ServiceLoader.html) for instance. – aioobe Sep 14 '15 at 17:33
  • 1
    What's wrong with doing a copy—that is, making a new Collection? You'd only be copying references, not making new objects. – VGR Sep 14 '15 at 19:35
  • Nothing terribly wrong, but I was looking for a way to avoid doing any allocations. As the name implies, [`Lists.newArrayList`](http://grepcode.com/file/repo1.maven.org/maven2/com.google.guava/guava/r06/com/google/common/collect/Lists.java#Lists.newArrayList%28java.lang.Iterable%29) can allocate a new array, potentially `log(n)` times as it does not get a size before allocating the backing array. (Although looking at the Guava code again, I just realized my own `instanceof then cast` is redundant.) – Patrick M Sep 15 '15 at 00:21
  • 1
    Just be aware that it's still possible for the caller to update `keys` after calling this method. If that breaks your assumptions, a copy might be preferable in all cases. – Brandon Sep 15 '15 at 02:19

6 Answers6

13

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.

Sergey Kalinichenko
  • 714,442
  • 84
  • 1,110
  • 1,523
11

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.

Community
  • 1
  • 1
Andreas
  • 154,647
  • 11
  • 152
  • 247
10

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.

Paul Boddington
  • 37,127
  • 10
  • 65
  • 116
  • I understand this can be proven to work, but these are all bad practices. In particular, `@SuppressWarnings` assumes that all future coders who maintain this method will never make a mistake. – VGR Sep 14 '15 at 19:29
  • 1
    @VGR That's why I said include a comment to explain why it's ok. `@SuppressWarnings("unchecked")` is used throughout the JDK, and it is for the situation when you can prove something is safe, but the compiler can't - exactly this situation. What are the other bad practices? – Paul Boddington Sep 14 '15 at 19:31
  • The unchecked cast. It's only "perfectly sound" in the logical sense, and that will probably be the first thing to break when someone else steps in to modify the code in some way. I would liken it to saying "I don't need a seat belt because I'm a good driver." – VGR Sep 14 '15 at 19:38
  • @VGR As I say there are loads of unchecked casts in the standard libraries. For example `Optional` uses one to be able to return the same instance for `Optional.empty()` no matter what type you supply. The alternative would be to create a new object every time. It's similar to the OP's question. Yes, you could copy the `List` but we don't know how long the `List` is, or whether the method is supposed to mutate the argument. In my opinion the only bit of bad advice in my answer was that I said to apply the `@SuppressWarnings` to the method (I changed this to statement). – Paul Boddington Sep 14 '15 at 19:51
  • I'm with @PaulBoddington. The language lacks the necessary tools to avoid unchecked casts 100% of the time. You can push them around; sometimes you can push them into the JRE, but you can't avoid them. – bmargulies Sep 14 '15 at 20:02
  • For your last point, you might want to point out that it's because `Collection` was added in Java 1.2, but `Iterable` wasn't added until Java 5, and that most existing methods taking a `Collection` were not retrofitted for backward compatibility reasons. – Andreas Sep 14 '15 at 20:38
  • Just because the Java SE library does it doesn't mean it's okay for you and me to do it. Java SE classes are general-purpose classes and they undergo an extremely rigorous suite of regression testing before release. – VGR Sep 14 '15 at 21:00
  • @bmargulies I disagree. I have never had to do an unsafe cast. There is always a way to avoid it, without generating warnings or using `@SuppressWarnings`. – VGR Sep 14 '15 at 21:01
  • @Andreas Thanks for that. Edit made. I had no idea `Iterable` was so recent. – Paul Boddington Sep 14 '15 at 21:10
  • Agreed - this is a perfectly legit case for casting; there are a lot of parties to blame here, but OP is not at fault :) – ZhongYu Sep 14 '15 at 22:23
  • Even if `siteDBDao.getSitesById()` accepts Iterable, it is highly unlikely that it will accept the wildcard form. The fault is Java's not having variant types, and we are justified to just do the cast. There are theoretically more correct solutions, but what for? I wrote about this topic [here](http://bayou.io/draft/Wildcard_Case_Studies.html#Tight_Bound) and [here](http://bayou.io/draft/Wildcard_Case_Studies.html#Missing_Wildcards) – ZhongYu Sep 14 '15 at 22:29
  • 1
    Everyone might be interested to know that Guava's helper [`Lists.newArrayList`](http://grepcode.com/file/repo1.maven.org/maven2/com.google.guava/guava/r06/com/google/common/collect/Lists.java#Lists.newArrayList%28java.lang.Iterable%29) method that I was using does essentially the same unchecked cast beneath the covers. – Patrick M Sep 27 '15 at 03:13
5

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.

ZhongYu
  • 19,446
  • 5
  • 33
  • 61
  • 1
    I fail to see how some of these represent "bad designs", could you explain further? – Octavia Togami Sep 14 '15 at 23:00
  • 1
    @ıɯɐƃoʇǝızuǝʞ - "arguably" :) To me, `for(x : sqlEx)` is bad because the meaning isn't immediately clear. It would be better to design it like `for(x: sqlEx.causes())` – ZhongYu Sep 14 '15 at 23:06
  • Usefulness of those classes aside, reusing something so non-intuitively named for test code would be a bad design. +1 for enumerating the choices. – Patrick M Sep 15 '15 at 00:23
1

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]
Community
  • 1
  • 1
h.j.k.
  • 1,357
  • 2
  • 20
  • 30
1

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());
  }
}
Patrick M
  • 10,547
  • 9
  • 68
  • 101