7

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 });
0

6 Answers 6

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.

Sign up to request clarification or add additional context in comments.

Comments

6

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

1 Comment

I'd do Stream.of(One.SET, Two.SET, Three.SET) rather than ImmutableSet.of().stream() to avoid unnecessarily creating the interim ImmutableSet.
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)

3 Comments

That definitely works, but is there a way while specifying the generic type?
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.
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.
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.

Comments

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.

1 Comment

warning: [unchecked] Possible heap pollution from parameterized vararg type Set<T>
0

This should help. One liner in Java 8:

Stream.of(sets).flatMap(Set::stream).collect(Collectors.toSet())

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.