Home » Java » How to initialize HashSet values by construction?

How to initialize HashSet values by construction?

Posted by: admin November 2, 2017 Leave a comment

Questions:

I need to create a Set with initial values.

Set<String> h = new HashSet<String>();
h.add("a");
h.add("b");

Is there a way to do this in one line of code?

Answers:

There is a shorthand that I use that is not very time efficient, but fits on a single line:

Set<String> h = new HashSet<>(Arrays.asList("a", "b"));

Again, this is not time efficient since you are constructing an array, converting to a list and using that list to create a set.

When initializing static final sets I usually write it like this:

public static final String[] SET_VALUES = new String[] { "a", "b" };
public static final Set<String> MY_SET = new HashSet<>(Arrays.asList(SET_VALUES));

Slightly less ugly and efficiency does not matter for the static initialization.

Questions:
Answers:

Collection literals were scheduled for Java 7, but didn’t make it in. So nothing automatic yet.

You can use guava’s Sets:

Sets.newHashSet("a", "b", "c")

Or you can use the following syntax, which will create an anonymous class, but it’s hacky:

Set<String> h = new HashSet<String>() {{
    add("a");
    add("b");
}};

Questions:
Answers:

In Java 8 I would use:

Set<String> set = Stream.of("a", "b").collect(Collectors.toSet());

This gives you a mutable Set pre-initialized with “a” and “b”. Note that while in JDK 8 this does return a HashSet, the specification doesn’t guarantee it, and this might change in the future. If you specifically want a HashSet, do this instead:

Set<String> set = Stream.of("a", "b")
                        .collect(Collectors.toCollection(HashSet::new));

Questions:
Answers:

There are a few ways:

Double brace initialization

This is a technique which creates an anonymous inner class which has an instance initializer which adds Strings to itself when an instance is created:

Set<String> s = new HashSet<String>() {{
    add("a");
    add("b");
}}

Keep in mind that this will actually create an new subclass of HashSet each time it is used, even though one does not have to explicitly write a new subclass.

A utility method

Writing a method that returns a Set which is initialized with the desired elements isn’t too hard to write:

public static Set<String> newHashSet(String... strings) {
    HashSet<String> set = new HashSet<String>();

    for (String s : strings) {
        set.add(s);
    }
    return set;
}

The above code only allows for a use of a String, but it shouldn’t be too difficult to allow the use of any type using generics.

Use a library

Many libraries have a convenience method to initialize collections objects.

For example, Google Collections has a Sets.newHashSet(T...) method which will populate a HashSet with elements of a specific type.

Questions:
Answers:

You can do it in Java 6:

Set<String> h = new HashSet<String>(Arrays.asList("a", "b", "c"));

But why? I don’t find it to be more readable than explicitly adding elements.

Questions:
Answers:

Using Java 8 (Modifiable Sets)

Using Stream in Java 8.

Set<String> strSet1 = Stream.of("A", "B", "C", "D")
         .collect(Collectors.toCollection(HashSet::new));

// stream from an array (String[] stringArray)
Set<String> strSet2 = Arrays.stream(stringArray)
         .collect(Collectors.toCollection(HashSet::new));

// stream from a list (List<String> stringList)
Set<String> strSet3 = stringList.stream()
         .collect(Collectors.toCollection(HashSet::new));

Using Java 8 (Unmodifiable Sets)

In case we are interested in unmodifiable sets then we have an options to use Collections.unmodifiableSet as:

Set<String> strSet4 = Collections.unmodifiableSet(strSet1);

But it looks slightly awkward and we can write our own collector like this:

class ImmutableCollector {
    public static <T> Collector<T, Set<T>, Set<T>> toImmutableSet() {
        return Collector.of(HashSet::new, Set::add, (l, r) -> {
            l.addAll(r);
            return l;
        }, Collections::unmodifiableSet);
    }
}

And then use it as:

Set<String> strSet4 = Stream.of("Apple", "Ball", "Cat", "Dog")
             .collect(ImmutableCollector.toImmutableSet());

Using Java 9 (Unmodifiable Sets)

Set<String> strSet5 = Set.of("Apple", "Ball", "Cat", "Dog");

Questions:
Answers:

If you have only one initial value in set this would be enough:

Set<String> h = Collections.singleton("a");

Questions:
Answers:

I feel the most readable is to simply use google Guava:

Set<String> StringSet = Sets.newSet("a", "b", "c");

Questions:
Answers:

A generalization of coobird’s answer’s utility function for creating new HashSets:

public static <T> Set<T> newHashSet(T... objs) {
    Set<T> set = new HashSet<T>();
    for (T o : objs) {
        set.add(o);
    }
    return set;
}

Questions:
Answers:

One of the most convenient ways is usage of generic Collections.addAll() method, which takes a collection and varargs:

Set<String> h = new HashSet<String>();
Collections.addAll(h, "a", "b");

Questions:
Answers:

If the contained type of the Set is an enumeration then there is java built factory method (since 1.5):

Set<MY_ENUM> MY_SET = EnumSet.of( MY_ENUM.value1, MY_ENUM.value2, ... );

Questions:
Answers:

With Eclipse Collections there are a few different ways to initialize a Set containing the characters ‘a’ and ‘b’ in one statement. Eclipse Collections has containers for both object and primitive types, so I illustrated how you could use a Set<String> or CharSet in addition to mutable, immutable, synchronized and unmodifiable versions of both.

Set<String> set =
    Sets.mutable.with("a", "b");
HashSet<String> hashSet =
    Sets.mutable.with("a", "b").asLazy().into(new HashSet<String>());
Set<String> synchronizedSet =
    Sets.mutable.with("a", "b").asSynchronized();
Set<String> unmodifiableSet =
    Sets.mutable.with("a", "b").asUnmodifiable();

MutableSet<String> mutableSet =
    Sets.mutable.with("a", "b");
MutableSet<String> synchronizedMutableSet =
    Sets.mutable.with("a", "b").asSynchronized();
MutableSet<String> unmodifiableMutableSet =
    Sets.mutable.with("a", "b").asUnmodifiable();

ImmutableSet<String> immutableSet =
    Sets.immutable.with("a", "b");
ImmutableSet<String> immutableSet2 =
    Sets.mutable.with("a", "b").toImmutable();

CharSet charSet =
    CharSets.mutable.with('a', 'b');
CharSet synchronizedCharSet =
    CharSets.mutable.with('a', 'b').asSynchronized();
CharSet unmodifiableCharSet =
    CharSets.mutable.with('a', 'b').asUnmodifiable();
MutableCharSet mutableCharSet =
    CharSets.mutable.with('a', 'b');
ImmutableCharSet immutableCharSet =
    CharSets.immutable.with('a', 'b');
ImmutableCharSet immutableCharSet2 =
    CharSets.mutable.with('a', 'b').toImmutable();

Eclipse Collections is compatible with Java 5 – 8.

Note: I am a committer for Eclipse Collections.

Questions:
Answers:

With Java 9 you can do the following:

Set.of("a", "b");

and you’ll get an immutable Set containing the elements. For details see this link.

Questions:
Answers:

A bit convoluted but works from Java 5:

Set<String> h = new HashSet<String>(Arrays.asList(new String[] {  
    "a", "b"
}))

Use a helper method to make it readable:

Set<String> h = asSet ("a", "b");

public Set<String> asSet(String... values) {
    return new HashSet<String>(java.util.Arrays.asList(values));
}

Questions:
Answers:

Just a small note, regardless of which of the fine approaches mentioned here you end up with, if this is a default that usually goes unmodified (like a default setting in a library you are creating), it is a good idea to follow this pattern:

// Initialize default values with the method you prefer, even in a static block
// It's a good idea to make sure these defaults aren't modifiable
private final static Set<String> DEFAULT_VALUES = Collections.unmodifiableSet(...);
private Set<String> values = DEFAULT_VALUES;

The benefit depends on the number of instances you create of that class and how likely it’s that defaults will be changed.

If you decide to follow this pattern, then you also get to pick the method of set initialization that’s most readable. As the micro differences in efficiency between the different methods will probably not matter much as you will be initializing the set only once.

Questions:
Answers:
import com.google.common.collect.Sets;
Sets.newHashSet("a", "b");

or

import com.google.common.collect.ImmutableSet;
ImmutableSet.of("a", "b");

Questions:
Answers:

(ugly) Double Brace Initialization without side effects:

Set<String> a = new HashSet<>(new HashSet<String>() {{
    add("1");
    add("2");
}})

But in some cases, if we mentioned that is a good smell to make final collections unmutable, it could be really useful:

final Set<String> a = Collections.unmodifiableSet(new HashSet<String>(){{
    add("1");
    add("2");
}})

Questions:
Answers:

This is an elegant solution:

public static final <T> Set<T> makeSet(@SuppressWarnings("unchecked") T... o) {
        return new HashSet<T>() {
            private static final long serialVersionUID = -3634958843858172518L;
            {
                for (T x : o)
                   add(x);
            }
        };
}

Questions:
Answers:

Can use static block for initialization:

private static Set<Integer> codes1=
        new HashSet<Integer>(Arrays.asList(new Integer[] { 1, 2, 3, 4}));

private static Set<Integer> codes2 =
        new HashSet<Integer>(Arrays.asList(new Integer[] { 5, 6, 7, 8}));

private static Set<Integer> h = new HashSet<Integer>();

static{
    h.add(codes1);
    h.add(codes2);
}

Questions:
Answers:

With the release of and the convenience factory methods this is possible in a cleaner way as:

Set set2 = Set.of("a", "b", "c");