
Learn through the super-clean Baeldung Pro experience:
>> Membership and Baeldung Pro.
No ads, dark-mode and 6 months free of IntelliJ Idea Ultimate to start with.
Last updated: March 18, 2024
In this short tutorial, we’ll learn what the @switch annotation is in Scala, along with its semantics and use cases with the help of relevant examples. We’ll also see the compiler behavior in various situations and the associated performance implications.
The Scala compiler does certain performance optimizations for pattern matching by treating it like a switch statement in Java. The compiler optimizes by compiling the pattern match into a branch table, thus making it much faster than a simple decision tree that uses multiple if statements.
We can apply the @switch annotation to a match statement in Scala. We can use the @switch annotation to make sure that the compiler applies the optimization to our pattern match expression. The compiler emits a warning message if it’s not able to do any optimization.
A match expression can be compiled into three different ways: a tableswitch, lookupswitch, or decision tree (chain of multiple if statements).
Both tableswitch and lookupswitch use branch tables to store case values and pick the matching value based on a label. The Scala compiler decides which one to use based on how contiguous the values are in the case statements. These operations have time complexities O(1) and O(log n), respectively.
This is the primary and preferred optimization method of the two. It uses a table of values with labels to pick corresponding values. Then these labels are used for a computed direct jump. Therefore, it’s a constant time O(1) operation, and it’s a faster optimization than lookupswitch. The case statements’ values must be contiguous for this optimization to happen.
Let’s look at an example of pattern matching in which the case values are continuous integers without any gaps:
val numWheels = 2
(numWheels : @switch) match {
case 1 => println("MonoWheeler")
case 2 => println("TwoWheeler")
case 3 => println("ThreeWheeler")
case 4 => println("FourWheeler")
case _ => println("UnKnown")
}
Let’s disassemble our class file using javap command and look at the bytecode:
16: tableswitch { // 1 to 6
1: 56
2: 67
3: 78
4: 89
5: 111
6: 100
default: 111
}
We can see that the compiler has applied the optimization as we expected.
If the case statements’ values are not contiguous and have gaps in between, the compiler will use a lookupswitch instead of a tableswitch. Here, the compiler uses sorted keys and labels for lookup. Then, it applies a binary search on the sorted keys to finding a matching value. Hence, it’s an O(log n) operation.
Let’s look at an example of pattern matching of non-contiguous char values:
val alphabet = 'A'
(alphabet : @switch) match {
case 'A' => println("ANIMAL")
case 'E' => println("ELEPHANT")
case 'I' => println("IRON")
case 'O' => println("OWL")
case 'U' => println("UMBRELLA")
case _ => println("Not a Vowel")
}
Let’s look at the bytecode once again:
12: lookupswitch { // 5
65: 64
69: 75
73: 86
79: 97
85: 108
default: 119
}
This time, we observe that the compiler has used a lookupswitch instead of a tableswitch.
We’ve seen how @switch can lead to optimizations in some simple examples. However, there are certain caveats for these optimizations. The Scala compiler applies the @switch optimization only if the following conditions are satisfied:
Let’s observe the compiler behavior when we apply the @switch annotation to a match statement using reference values:
val gender = 1
val male = 0
val female = 1
(gender : @switch) match {
case `male` => println("Male")
case `female` => println("Female")
case _ => println("Invalid")
}
We’ll get a warning message from the compiler indicating that it couldn’t emit the @switch optimization:
NotOptimizedExample.scala:10: warning: could not emit switch for @switch annotated match
(gender : @switch) match { // Compiler gives a warning: could not emit switch for @switch annotated match
^
one warning found
In this tutorial, we’ve seen how Scala’s @switch annotation helps us to ensure the compiler is optimizing for our pattern match statements. We learned the performance gains we get by using a tableswitch or a lookupswitch.
Then we listed all the cases in which the compiler won’t apply any optimizations and thus uses simple branching statements.