short
, but that can be implicitly cast to an int
. In this manner, the values have to be consistent with size
, but they do not all have to be the same data type. The last three case
expressions do not compile because each returns a type that cannot be assigned to the int
variable.
Applying a case Block
A switch
expression supports both an expression and a block in the case
and default
branches. Like a regular block, a case
block is one that is surrounded by braces ({}
). It also includes a yield
statement if the switch
expression returns a value. For example, the following uses a mix of case
expressions and blocks:
int fish = 5; int length = 12; var name = switch(fish) { case 1 -> "Goldfish"; case 2 -> {yield "Trout";} case 3 -> { if(length > 10) yield "Blobfish"; else yield "Green"; } default -> "Swordfish"; };
The yield
keyword is equivalent to a return
statement within a switch
expression and is used to avoid ambiguity about whether you meant to exit the block or method around the switch
expression.
Referring to our second rule for switch
expressions, yield
statements are not optional if the switch
statement returns a value. Can you see why the following lines do not compile?
10: int fish = 5; 11: int length = 12; 12: var name = switch(fish) { 13: case 1 -> "Goldfish"; 14: case 2 -> {} // DOES NOT COMPILE 15: case 3 -> { 16: if(length > 10) yield "Blobfish"; 17: } // DOES NOT COMPILE 18: default -> "Swordfish"; 19: };
Line 14 does not compile because it does not return a value using yield
. Line 17 also does not compile. While the code returns a value for length
greater than 10
, it does not return a value if length
is less than or equal to 10
. It does not matter that length
is set to be 12
; all branches must yield
a value within the case
block.
Watch Semicolons in switch Expressions
Unlike a regular switch
statement, a switch
expression can be used with the assignment operator and requires a semicolon when doing so. Furthermore, semicolons are required for case
expressions but cannot be used with case
blocks.
var name = switch(fish) { case 1 -> "Goldfish" // DOES NOT COMPILE (missing semicolon) case 2 -> {yield "Trout";}; // DOES NOT COMPILE (extra semicolon) … } // DOES NOT COMPILE (missing semicolon)
A bit confusing, right? It's just one of those things you have to train yourself to spot on the exam.
Covering All Possible Values
The last rule about switch
expressions is probably the one the exam is most likely to try to trick you on: a switch
expression that returns a value must handle all possible input values. And as you saw earlier, when it does not return a value, it is optional.
Let's try this out. Given the following code, what is the value of type
if canis
is 5
?
String type = switch(canis) { // DOES NOT COMPILE case 1 -> "dog"; case 2 -> "wolf"; case 3 -> "coyote"; };
There's no case
branch to cover 5
(or 4
, -1
, 0
, etc.), so should the switch
expression return null
, the empty string, undefined, or some other value? When adding switch
expressions to the Java language, the authors decided this behavior would be unsupported. Every switch
expression must handle all possible values of the switch
variable. As a developer, there are two ways to address this:
Add a default branch.
If the switch expression takes an enum value, add a case branch for every possible enum value.
In practice, the first solution is the one most often used. The second solution applies only to switch
expressions that take an enum. You can try writing case
statements for all possible int
values, but we promise it doesn't work! Even smaller types like byte
are not permitted by the compiler, despite there being only 256 possible values.
For enums, the second solution works well when the number of enum values is relatively small. For example, consider the following enum definition and method:
enum Season {WINTER, SPRING, SUMMER, FALL} String getWeather(Season value) { return switch(value) { case WINTER -> "Cold"; case SPRING -> "Rainy"; case SUMMER -> "Hot"; case FALL -> "Warm"; }; }
Since all possible permutations of Season
are covered, a default
branch is not required in this switch
expression. You can include an optional default
branch, though, even if you cover all known values.
switch
expressions that use the enum without a default
branch will suddenly fail to compile. If this was done frequently, you might have a lot of code to fix! For this reason, consider including a default
branch in every switch
expression, even those that involve enum values.
Writing while Loops
A common practice when writing software is doing the same task some number of times. You could use the decision structures we have presented so far to accomplish this, but that's going to be a pretty long chain of if
or else
statements, especially if you have to execute the same thing 100 times or more.
Enter loops! A loop is a repetitive control structure that can execute a statement of code multiple times in succession. By using variables that can be assigned new values, each repetition of the statement may be different. The following loop executes exactly 10 times:
int counter = 0; while (counter < 10) { double price = counter * 10; System.out.println(price); counter++; }
If you don't follow this code, don't panic—we cover it shortly. In this section, we're going to discuss the while
loop and its two forms. In the next section, we move on to for
loops, which have their roots in while
loops.
The while Statement
The simplest repetitive control structure in Java is the while
statement, described in Figure 3.5. Like all repetition control structures, it has a termination condition, implemented as a boolean
expression, that will continue as long as the expression evaluates to true
.
FIGURE 3.5 The structure of a while
statement
As shown in Figure 3.5, a while
loop is similar to an if
statement in that it is composed of a boolean
expression and a statement, or a block of statements. During execution, the boolean
expression is evaluated before each iteration of the loop and exits if the evaluation returns false
.
Let's see how a loop can be used to model a mouse eating