Don’t use Set.of().contains(). Use switch instead.

Set.of(a, b, c).contains(value) might seem clever and while it is easy to read it’s way slower than switch(x) {case a,b,c -> true}.

We often have to check if a value is one of a given set of values. But we don’t want to write code like this:

if (value == SomeEnum.FOO || value == SomeEnum.BAR || value == SomeEnum.QUX) { ...

Some use a Set and write it like this:

if (Set.of(SomeEnum.FOO, SomeEnum.BAR, SomeEnum.QUX).contains(value)) { ...

But now the runtime has to create a Set each time this line is run and it also has to call the method contains. And it can’t even handle null. In Java we have a much better alternative: The switch expression. You can use it like so:

if (switch (value) { case FOO, BAR, QUX -> true; default -> false; }) { ...

This is much faster. Sure, you could extract the Set.of to a static field, but I have tested that and it makes almost no difference. It seems Java optimises this so perfectly, that it doesn’t have to actually create a new Set each time the code is executed. You could also use static imports, but that doesn’t really solve the performance issue.

Another advantage of the switch statement is that it can handle null. Just add case null, default -> false and you are done. Set.of().contains(null) on the other hand always gives you a NullPointerException when the value is null.

You might still want to use Set.of() if it’s not about Enums, Strings or primitives. It works well as long as you don’t forget about null.

Here’s the code I uses for the comparison. Note that results might be quite different on another system, but it was always at least 5 times faster on my PC. Sometimes even 50 times faster. In one case it was even optimised so well that the complete loop was skipped as Java figured out that it wouldn’t do anything. That’s why I added the volatile field, which forces Java to actually execute the code so that other threads might see it.

package ch.claude_martin;

import static java.time.temporal.ChronoUnit.*;

import java.time.temporal.ChronoUnit;
import java.util.Set;

public class Main {
	// "volatile" forces Java to actually write to memory
	private static volatile boolean field = false;

	public static void main(String[] args) {
		for (int x = 0; x < 10; x++) {
			var start = System.nanoTime();

			var values = ChronoUnit.values();

			int reps = 10_000_000;

			for (int i = 0; i < reps; i++) {
				var value = values[i % values.length];
				field = Set.of(CENTURIES, DAYS, DECADES, HOURS, MILLIS, WEEKS, MONTHS).contains(value);
			}
			var stop = System.nanoTime();

			long setOfContains = stop - start;
			System.out.print("Set.of(...).contains(value): ");
			System.out.println(setOfContains);

			var set = Set.of(CENTURIES, DAYS, DECADES, HOURS, MILLIS, WEEKS, MONTHS);
			for (int i = 0; i < reps; i++) {
				var value = values[i % values.length];
				field = set.contains(value);
			}
			stop = System.nanoTime();

			long setContains = stop - start;
			System.out.print("set.contains(value): ");
			System.out.println(setOfContains);

			start = System.nanoTime();

			for (int i = 0; i < reps; i++) {
				var value = values[i % values.length];
				field = switch (value) {
				case CENTURIES, DAYS, DECADES, HOURS, MILLIS, WEEKS, MONTHS -> true;
				case null, default -> false;
				};
			}
			stop = System.nanoTime();
			long switchCase = stop - start;

			System.out.print("switch (value) {}: ");
			System.out.println(switchCase);

			System.out.print("Difference: ");
			System.out.println(setContains - switchCase);

			System.out.print("Factor: ");
			System.out.println(((double) setContains) / (double) switchCase);
			System.out.println("=================================");

			System.out.flush();

		}

		if (field)
			System.out.println("The end.");
	}
}

Conclusion

Consider using switch expressions instead of Set.of() for better performance and easier handling of null references.

Leave a Reply

Your email address will not be published. Required fields are marked *