Friday, February 11, 2022

Design implications of Java’s switch statements and switch expressions

The two constructs might look similar, but they behave quite differently, and each is best suited for a different type of programmatic logic.

Download a PDF of this article

The recent evolution of the Java language has introduced interesting new features and constructs intended to make developers more productive. And it is one of these features, known as switch expressions, that I’d like to talk about.

I will start with the following assertion: switch statements and switch expressions have different design purposes. One is not just a different version of the other. Rather, switch expressions are a new construct intended for a different purpose than switch statements. To be clear, the older switch statement and the newer switch expression share some syntactical similarities, but they pursue significantly different design goals.

My intention in this article is not to just talk about this new construct from the perspective of its syntax but rather investigate its design implications for developers’ work. Surely, the intended developer productivity improvements are not meant to be measured in how fast you can type but rather in how well Java’s language constructs support coding requirements and help developers convey the meaning of the algorithm they are working on. This means that every Java developer needs to understand the intended purposes of such language constructs.

switch statements are not an alternative if/else construct

Let’s start by addressing one of the most common misconceptions about the switch statement, namely that it is usually explained by comparing it to the if/else statement as an alternative mechanism for implementing conditional logic.

In my opinion, this is not really the best way of understanding the switch statement. The if/else construct is designed to choose an alternate execution path, essentially presenting a binary fork mechanism that selects the way in which program logic would flow, as shown in Figure 1.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 1. The if/else construct chooses an alternate execution path.

However, the switch statement feels much more like a forward-only goto mechanism, as shown in Figure 2. It is not about a binary selection of an alternate execution path but rather a way of executing a jump into a block of code at a specified line labeled using a corresponding case. This means that switch statement logic is more like a goto operator than an if/else construct.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 2. The switch statement acts like a forward-only goto mechanism.

Once you understand this principle, the fall-through behavior of the switch statement appears fairly natural: When the switch statement evaluates a value that corresponds to a particular case statement, the execution path jumps directly to that case statement and execution continues from there.

After all, you certainly would expect an algorithm to continue progressing forward as usual after a goto action. As a matter of fact, this could even be exploited to your advantage.

Suppose you need to perform several actions that are not mutually exclusive, and yet not all the actions are always applicable. For example, imagine that you need to set a pricing discount based on product status, as follows (see Figure 3):

◉ For discontinued products, the discount should be increased by 0.2.
◉ For refurbished products, the discount should be increased by 0.1.
◉ For new products, the discount should be decreased by 0.1.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 3. Example of a "classic" switch statement

In addition to the above rules, there are two extra business constraints:

◉ Some discontinued products may also be refurbished, in which case both discounts should be applied.
◉ New products can’t be classified as refurbished or discontinued.

Notice that the first two cases (for discontinued and refurbished products) have an additive nature rather than a mutually exclusive nature. The use of the fall-through switch/case construct is perfectly suitable to reflect this type of business requirement, and the resulting code is more readable than an attempt to express the same logic with a complicated if/else construct. Of course, the same logic could be expressed using several if conditions, but it is obvious that the resulting code would be less straightforward and possibly harder to read, harder to test, and harder to maintain.

I believe in the great value of source code readability. Often, the same logic can be expressed in a program in many ways, so it may be worth considering readability as an important deciding factor when you code constructs. After all, code written in a convoluted and confusing fashion can be prone to errors and misinterpretations.

Selecting the best-matching semantical construct in a program is probably as important as in a human conversation: If you want to be understood, you should express your thoughts clearly, concisely, and unambiguously. (Similarly, coding is a conversation between you and the compiler—and also between you and whoever will be maintaining your code in the future.)

switch expression case statements don’t fall-through


Now let’s consider the design ideas behind a switch expression. The first thing to notice is that switch expression case statements are mutually exclusive and do not use the fall-through mechanism. That is because of the different purpose that switch expression design pursues, which is to derive and return a single value.

The expectation is that executing a switch expression yields a single value, and that case statements within the switch simply represent different ways of deriving that value, as shown in Figure 4.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 4. The switch expression uses case to represent different value derivations

The need to return a single value implies the mutually exclusive nature of these case statements; thus, switch expression case statements do not fall through and also they do not allow the use of a break statement.

Consider a business scenario where a product’s discount is dependent on a single, specific case. The main difference from the earlier example is that the discount for the discontinued and refurbished products is the same, but discount values are not added one after another, as shown in Figure 5. The -> arrow token indicates that you are using a switch expression rather than a switch statement.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 5. Example of using a switch expression to derive a value

Of course, this switch is now capable of returning a value, and it also needs to be terminated with a semicolon.

The style of the switch expression makes it somewhat like a SQL decode operator, treating the entire switch as a kind of value-derivation function.

There is one more consideration that is checked by a compiler, which is the need to guarantee that no matter what value is yielded from the expression, the list of case statements needs to be exhaustive. In other words, one case will always apply. This requires using a default case.

Another nice syntactical enchantment is the ability to create a comma-separated list of cases that should yield the same value. Clearly, this presents a good and nicely readable code construct, which perfectly reflects the intended purpose of conditionally deriving a value.

Interesting details about switch expressions


Technically speaking, returning a value from a switch expression is not a requirement, and it is also possible to construct more-complex switch expressions that create blocks of code to implement various cases. See Figure 6 and Figure 7.

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 6. Example of using code blocks in a switch expression

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 7. Example of using the yield operator to return a value from the code block in a switch expression

Another significant difference between the older switch statement and the newer style of switch expression is that in switch expressions, each case statement has its own scope and may declare local variables independently from other case statements. In the older switch statement, case statements are treated as goto targets that are merely code labels within the same block, and thus all case statements share the same scope.

An additional operator, yield, was introduced in the Java language to help deal with the situation where you’d like to create a switch expression that returns a value and at the same time uses blocks of code to implement cases. In these circumstances, yield must be used to return a value for the relevant case.

As you might imagine, adding this new keyword to an established programming language is an inherently awkward thing to do because of its potential of breaking existing code that might have used the keyword yield as an identifier, for example, as a variable name.

To avoid such confusion, the new yield operator is not considered to be a keyword anywhere in Java except within the switch expression construct. When the switch expression specification was formulated, there was a discussion regarding the use of the break operator instead for this purpose. However, despite some backward compatibility inconveniences, it was decided that using yield to return a value is better than repurposing break, because using break would result in a much more confusing set of syntactical rules and produce less-readable and potentially misleading code.

Pattern matching for switch


Another interesting emerging development related to the switch construct is an introduction of the pattern matching capabilities for switch (see Figure 8).

Oracle Java, Core Java, Oracle Java Exam Prep, Oracle Java Preparation, Java Certification, Oracle Java Skills
Figure 8. Preview of pattern matching with a switch statement

Pattern matching is already a production feature for the instanceof operator. However, for switch it is still a preview feature and is described by JEP 406. Although it is not yet a production feature, this preview feature shows the direction in which switch is evolving.

The idea is to allow a switch statement or switch expression to accept an object and then trigger a specific case statement that matches this object type. Simultaneously, the object reference is cast to the appropriate type, and a locally scoped variable is introduced to the case statement to represent this casted object reference.

Please note that the actual syntax and implementation details for switch pattern matching can change in the future because this feature is not yet in production.

Source: oracle.com

Related Posts

0 comments:

Post a Comment