6

I have multiple Set<String> that I need to merge into one Set<String>. How do I do this operation in Java? Note, I am using using the guava API as best as I can to help out. For example, I have 3 classes as follows.

public class One {
 public static Set<String> SET = Sets.newHashSet("a","b","c");
}
public class Two {
 public static Set<String> SET = Sets.newHashSet("a","d","e","f");
}
public class Three {
 public static Set<String> SET = Sets.newHashSet("w","x","y","f");
}

Now, I need to merge any combination of these sets into one. For, example, I may need to merge

  • One.SET + Two.SET + Three.SET into one to produce { "a","b","c","d","e","f","w","x","y" },
  • One.SET + Three.SET into one to produce { "a","b","c","w","x","y","f" },
  • Two.SET + Three.SET into one to produce { "a","d","e","f","w","x","y" },
  • and so on

I created a method to merge an array of sets, Set<String>[], but that doesn't work (explained here by this SO post Creating an array of Sets in Java). Here's the code to merge. It works (compiles).

public static Set<String> immutableSetOf(Set<String>[] sets) {
 Set<String> set = new HashSet<String>();
 for(Set<String> s : sets) {
  set.addAll(s);
 }
 return ImmutableSet.copyOf(set);
}

Here's the calling code; it doesn't work (doesn't compile).

Set<String> set = Utils.immutableSetOf(new Set<String>[] { One.SET, Two.SET });

So, I modified my merging method to operate on List<Set<String>> instead of Set<String>[]. Only the argument type changed, but I put it here for completeness.

public static Set<String> immutableSetOf(List<Set<String>> sets) {
 Set<String> set = new HashSet<String>();
 for(Set<String> s : sets) {
  set.addAll(s);
 }
 return ImmutableSet.copyOf(set);
}

So, now my calling code looks like the following.

Set<String> set = Utils.immutableSetOf(
 Lists.newArrayList(
  One.SET, Two.SET));

This code does not compile, since Lists.newArrayList(...) is returning Set<String> and not List<Set<String>>. The method Lists.newArrayList(...) is overloaded, and the signature of the method that is used when I pass in sets is, List.newArrayList(Iterable<? extends E> elements).

So, the question is, how do I define a method to merge an arbitrary number of Set<String> while considering the calling code? I note that the compilation problems are on the calling code (not the merging method), but perhaps the solution also relates to the merging code?

Update: I also tried varargs but that produces its own warning (Is it possible to solve the "A generic array of T is created for a varargs parameter" compiler warning?). The merging method signature is now the following.

public static Set<String> immutableSetOf(Set<String>... sets)

The calling code is now the following, and I get "Type safety: A generic array of Set is created for a varargs parameter".

Set<String> set = Utils.immutableSetOf(One.SET, Two.SET);

Update: For the accepted answer, I did the following.

@SuppressWarnings("unchecked")
Set<String> set = Utils.immutableSetOf(Set[] { One.SET, Two.SET });
Community
  • 1
  • 1
Jane Wayne
  • 6,828
  • 9
  • 57
  • 97

5 Answers5

6

Recommend com.google.common.collect.Sets#union(set1, set2) to get the merge instead of Set.addAll under hood, since Guava is already in your dependencies.

Reason: it's view which is memory effective, and also unmodifiable.

plus: I should have post it as a comment, sorry.

卢声远 Shengyuan Lu
  • 29,208
  • 21
  • 78
  • 123
3

How about creating a collection of your input sets, then using flatMap?

Set<String> allElements = ImmutableSet.of(
        One.SET,
        Two.SET,
        Three.SET
).stream().flatMap(Collection::stream).collect(Collectors.toSet());
Shannon S
  • 161
  • 2
  • 2
  • I'd do `Stream.of(One.SET, Two.SET, Three.SET)` rather than `ImmutableSet.of().stream()` to avoid unnecessarily creating the interim `ImmutableSet`. – M. Justin May 12 '21 at 20:14
2

In your previous attempt, you had written

Set<String> set = Utils.immutableSetOf(new Set<String>[] { One.SET, Two.SET });

This does not work because, generics array creation is not allowed in java

Try this:

@SuppressWarnings("unchecked")
Set<String>[] mySet = new Set[] { One.SET, Two.SET };
Set<String> set = Utils.immutableSetOf(mySet);

The reason this works is because, we create a new Set[] without specifying the generics type and assign it to the reference Set[] mySet (since this is an unchecked operation, we have to add @SuppressWarnings("unchecked") to it)

MJSG
  • 1,053
  • 8
  • 12
  • That definitely works, but is there a way while specifying the generic type? – Jane Wayne Dec 03 '14 at 04:44
  • From what i know, the java does not allow it. Also, there is no need for it, as at the end of the day, we need to have a reference with the specified type, which can be accomplished. – MJSG Dec 03 '14 at 04:44
  • 1
    Recommend com.google.common.collect.Sets#union(set1, set2) to get the merge instead of Set.addAll under hood, since Guava is already in your dependencies. Reason: it's view which is memory effective, and also unmodifiable. – 卢声远 Shengyuan Lu Dec 03 '14 at 05:06
1

You could use:

Set<String> set = immutableSetOf(Arrays.asList(One.SET, Two.SET));

Using your definition of immutableSetOf(List<Set<String>> sets). No warnings or suppressed warnings.

clstrfsck
  • 14,112
  • 4
  • 42
  • 56
1

I'm thinking that FluentIterable (in 18.0 and above) can help here, specifically the append method.

With that, we need to define a convenient helper method - and yes, we're going to use varargs for this.

public <T extends Comparable<T>> Set<T> mergeSets(Set<T> initial,
                                                 Set<T>... rest) {
    FluentIterable<T> result =  FluentIterable.from(initial);
    for(Set<T> set : rest) {
        result = result.append(set);
    }
    return new TreeSet<>(result.toSet());
}

The resultant set here is in natural order. If you don't want a TreeSet and you don't want to mutate your collection afterwards, then omit the new TreeSet<> piece, and loosen the bound on T.

Makoto
  • 96,408
  • 24
  • 164
  • 210