26

I want to initialize a Set Implementation (HashSet) in Java with an Iterable. However, the constructor of HashSet doesn't accept Iterables, but only Collections type objects.

Is there a way to convert from Iterable to some subtype of Collections.

Martin Schröder
  • 4,176
  • 7
  • 47
  • 81
VaidAbhishek
  • 5,895
  • 7
  • 43
  • 59
  • I think the question itself is different enough (since one is an ordered collection) or the other question is not general enough to support both ordered and unordered. – Archimedes Trajano Aug 21 '20 at 06:15
  • Well, the linkled duplicate answers the literal question asked by providing "a way to convert from Iterable to some subtype of Collection". However, I agree that this (constructing a temporary `ArrayList`) is not necessarily the best way to intialize a `HashSet` from an `Iterable`. – Hulk Aug 21 '20 at 11:10
  • If you have Spring Data on the class path you may use `Streamable.of(iterable).toSet()`. If Spring Data happens to be the source of the `Iterable` you have even more options: https://stackoverflow.com/a/67413334/66686 – Jens Schauder May 06 '21 at 07:28

7 Answers7

56

You can use Guava.

Set<T> set = Sets.newHashSet(iterable);

or to make it read like a sentence static import,

import static com.google.common.collect.Sets.*;

Set<T> set = newHashSet(iterable);
Iulian Popescu
  • 2,595
  • 4
  • 23
  • 31
ConnorWGarvey
  • 764
  • 4
  • 8
8

HashSet constructor relies on more than what Iterable offers: it wants to know the size of the collection up front in order to optimally construct the underlying HashMap. If you have a true, austere Iterable, which doesn't know its size, then you'll have to realize the Iterable up front by turning it into a regular Collection in any of a number of obvious ways.

If, on the other hand, you have a richer object that already knows its size, then it would pay to create a minimalist adapter class that wraps your Iterable into a collection, implementing just size in addition to forwarding the call to iterator.

public class IterableCollection<T> implements Collection<T>
{
   private final Iterable<T> iterable;

   public IterableCollection(Iterable<T> it) { this.iterable = it; }

   @Override public Iterator<T> iterator() { return iterable.iterator(); }

   @Override public int size() { return ... custom code to determine size ... }

   @Override .... all others ... { throw new UnsupportedOperationException(); }
}
Kirk Woll
  • 76,112
  • 22
  • 180
  • 195
Marko Topolnik
  • 195,646
  • 29
  • 319
  • 436
6

Sure, it's shown in this answer. Basically, iterate over the iterable and copy its contents in a collection:

public static <T> List<T> copyIterable(Iterable<T> iterable) {
    Iterator<T> iter = iterable.iterator();
    List<T> copy = new ArrayList<T>();
    while (iter.hasNext())
        copy.add(iter.next());
    return copy;
}

Use it as follows, the resulting List object can be passed as a parameter to the HashSet constructor.

Iterable<Integer> list = Arrays.asList(1, 2, 3);
List<Integer> copy = copyIterable(list);
Set<Integer> aSet = new HashSet<Integer>(copy);

EDIT

I've been mistaken all along. Iterable is a superinterface of Collection, so a simple (but unsafe) cast will do the trick, as long as the Iterable was a Collection to begin with.

Iterable<Integer> list = Arrays.asList(1, 2, 3);
Set<Integer> aSet = new HashSet<Integer>((Collection)list); // it works!
Community
  • 1
  • 1
Óscar López
  • 232,561
  • 37
  • 312
  • 386
  • I was avoiding to explicitly iterating over Iterable or consuming an Iterator myself. I'm looking for a mechanism so that it is acceptable to the constructor of the HashSet or any Set Implementation. (BTW: I didn't down vote you). But I would definitely upvote you if you can show me a workaround. :-) – VaidAbhishek May 08 '13 at 20:53
  • I'm getting this on my Netbeans. `no suitable constructor found for HashSet(Iterator)` – VaidAbhishek May 08 '13 at 21:00
  • @WChargin fixed it, thanks. please reconsider the downvote. – Óscar López May 08 '13 at 21:02
  • @VaidAbhishek pass the list, not the iterator – Óscar López May 08 '13 at 21:03
  • @VaidAbhishek I just realized how simple is the solution to this problem. Please take a look at the update, above – Óscar López May 08 '13 at 21:15
  • 3
    A `Collection` is an `Iterable`, but an `Iterable` is not necessarily a `Collection`. So, your cast is unsafe (as you stated) and could fail at runtime if the concrete `Iterable` was not also a `Collection`. – GriffeyDog May 08 '13 at 21:20
  • 2
    casting an Iterable to a Collection is a bad idea – matt b May 08 '13 at 21:22
  • @GriffeyDog I was just about to say that. I don't have a list. Infact, I have a custom DT, which exposes Iterator() and Iterable () functionality. – VaidAbhishek May 08 '13 at 21:22
  • @GriffeyDog I added a clarification stating this: "as long as the Iterable was a Collection to begin with" – Óscar López May 08 '13 at 21:23
  • @VaidAbhishek then, absolutely, you have no option but to copy your custom iterable DT to a proper `Collection`. You should have mentioned this from the beginning! – Óscar López May 08 '13 at 21:24
3

The Iterable interface allows the "foreach" syntax to work, so the cleanest way is likely:

public <T> Set<T> toSet(Iterable<T> collection) {
    HashSet<T> set = new HashSet<T>();
    for (T item: collection)
        set.add(item);
    return set;
}
Don Roby
  • 40,677
  • 6
  • 91
  • 113
2

Just add each one.

public static <T> Set<T> setFromIterable(Iterable<T> i) {
    HashSet<T> set = new HashSet<T>();
    Iterator<T> it = i.iterator();
    while (it.hasNext()) {
        set.add(it.next());
    }
    return set;
}

Iterable<Integer> someIterable = ...;
Set<Integer> someSet = setFromIterable(someIterable);

Note that you don't use the constructor new HashSet<Integer>(someIterator), because that doesn't exist. Just call the static method.

wchargin
  • 15,589
  • 12
  • 71
  • 110
2

I use this one-liner (with Java 8+), which only relies on java.util.stream:

StreamSupport.stream(myIterable.spliterator(), false).collect(Collectors.toSet());

// or with static imports:
stream(myIterable.spliterator(), false).collect(toSet());
Matthew Read
  • 1,365
  • 1
  • 30
  • 50
-3

Putting somewhat a repeated answer for conciseness. Below worked for me for converting the Iterable of String type to a Set(Java8).

Iterable<String> stringIterable = Arrays.asList("str1", "str2", "str3");
Set<String> stringHashSet = new HashSet<>((Collection<? extends String>) stringIterable);
Sandeep
  • 1
  • 2
  • This only works for a `Collection`, and if you already know that you have something more specific then you should type it correctly. – Matthew Read May 19 '21 at 16:14