Introduction

In this post, we will see what an EnumSet in Java is and when to use it. We will have a look at the various methods available as part of an EnumSet.

What is an EnumSet in Java

An EnumSet is a specialized Set implementation meant to be used with enum types. All the elements in an EnumSet are enum instances. Also, they must come from a single enum type. The type of the enum can be specified explicitly or implicitly (we will see how this is done).

Why EnumSet

We can use an EnumSet when we want to have a Set<SomeEnum>. Internally Enum sets are represented as bit vectors. This representation is more efficient than using a particular Set implementation like HashSet or a TreeSet.

Hence, the time and the space complexity of an EnumSet should be at least as good as other Set implementations. Thus, as a rule of thumb, use an EnumSet when a Set of enum is needed.

Ordering

The iterator returned by an EnumSet traverses the elements (enum constants) in their natural order i.e., the order in which the enum constants are declared.

Implementation

There are two concrete implementations of an EnumSet namely RegularEnumSet and JumboEnumSet. The EnumSet class is an abstract class and does not have any public constructors. It has a set of public static methods (static factory methods) to create EnumSet instances. (Note, this is not the same as a factory method pattern)

It determines which implementation to return based on the number of enum constants.

  • If the number of enum constants is less than or equal to 64, then a RegularEnumSet is returned.
  • If the number of enum constants is more than 64, then a JumboEnumSet is returned.

A RegularEnumSet internally uses a long variable as a bit vector. Thus, to determine if the ith enum constant is present in the set, it can simply check the ith bit of this long variable.

In a JumboEnumSet, it uses a long[] as bit vector representation. Determining the presence of an enum constant is a little more involved here.

The ith bit of the jth element of this array represents the presence of (64 * j + i) th enum

Hence, the first element of this bit vector can be used to keeping track of the first 64 enum constants. The second long value can be used for the next 64 values and so on.

Operations

Once we create an enum, we can perform all the operations supported by a Set. We will below look at the various options provided by an EnumSet for its creation.

EnumSet creation

It offers a lot of static factories to create an EnumSet. For our examples, we will use the Grade enum. It has one enum constant representing a range of score.

public enum Grade {
    S(91, 100),
    A(81, 90),
    B(71, 80),
    C(61, 70),
    D(51, 60),
    E(50, 50),
    F(0, 49);

    private int fromScore;
    private int toScore;

    Grade(int fromScore, int toScore) {
        this.fromScore = fromScore;
        this.toScore = toScore;
    }

    public int getFromScore() {
        return fromScore;
    }

    public int getToScore() {
        return toScore;
    }
}

EnumSet.allOf

Signature: <E extends Enum<E>> EnumSet<E> allOf(Class<E> elementType)

It creates an EnumSet having all the enum elements from the specified enum type.

Set<Grade> allGrades = EnumSet.allOf(Grade.class);
System.out.println(allGrades); //[S, A, B, C, D, E, F]

EnumSet.of

EnumSet in Java provides a set of static factories to create an EnumSet from an explicitly passed enum elements (overloadings of these method to support passing one through five enum constants). There is also a method that accepts a varargs of an enum. We can use this to pass an arbitrary number of elements.

Set<Grade> maxGrade = EnumSet.of(Grade.S);
Set<Grade> topThreeGrades = EnumSet.of(Grade.S, Grade.A, Grade.B);
Set<Grade> passGrades = EnumSet.of(Grade.S, Grade.A, Grade.B, Grade.C, Grade.D, Grade.E);
Set<Grade> failGrades = EnumSet.of(Grade.F);

EnumSet.complementOf

Signature: <E extends Enum<E>> EnumSet<E> complementOf(EnumSet<E> s)

This handy method is used to create an EnumSet having the elements from an enum that are not contained in the specified set.

EnumSet<Grade> failGrades = EnumSet.of(Grade.F);
Set<Grade> passGrades = EnumSet.complementOf(failGrades); //[S, A, B, C, D, E]

EnumSet.noneOf

Signature: <E extends Enum<E>> EnumSet<E> noneOf(Class<E> elementType)

It creates an empty enum set with the specified enum type.

Set<Grade> emptyGrades = EnumSet.noneOf(Grade.class);
System.out.println(emptyGrades); //[]

EnumSet.range

Signature: <E extends Enum<E>> EnumSet<E> range(E from, E to)

It can be used to create an EnumSet having all the elements in the specified range. The returned enum set will have the endpoints too (inclusive of from and the to). It throws an IllegalArgumentException if the from appears later than to in the enum. In other words, it throws IllegalArgumentException if from.compareTo(to) > 0.

Set<Grade> gradesFrom61To90 = EnumSet.range(Grade.A, Grade.C);
System.out.println(gradesFrom61To90); //[A, B, C]

EnumSet - copy and clone

Two other methods exist to create an EnumSet by copying an existing EnumSet or a Collection of an enum and by cloning an existing EnumSet.

Set<Grade> passGrades = EnumSet.of(Grade.S, Grade.A, Grade.B, Grade.C, Grade.D, Grade.E);
Set<Grade> passGradesCopy = EnumSet.copyOf(passGrades);
System.out.println(passGrades.equals(passGradesCopy)); //true
EnumSet<Grade> failGrades = EnumSet.of(Grade.F);
EnumSet<Grade> failGradesCloned = failGrades.clone();
System.out.println(failGrades.equals(failGradesCloned)); //true

Conclusion

In this article, we have learnt about what an EnumSet in Java is and when it can be used. We also covered the main creation methods available for creating an EnumSet with examples.

References