Friday, February 4, 2011

Chapter 27: if and switch Statements

The if and switch statements are commonly referred to as decision statements. When we use decision statements in our program, we’re asking the program to evaluate a given expression to determine which course of action to take.

Let us start with the if-else block

if-else Branching

The basic format of an if statement is as follows:
if (booleanExpression) {
System.out.println("Inside if statement");
}

The expression in parentheses must evaluate to a boolean value, either a true or false. The following code demonstrates a legal if-else statement:

if (x > 3) {
System.out.println("x is greater than 3");
} else {
System.out.println("x is not greater than 3");
}

The else block is optional, so you can also use the following:
if (x > 3) {
y = 2;
}
z += 8;
a = y + x;

The preceding code will assign 2 to y if the test succeeds (i.e., x really is greater than 3), but the other two lines will execute regardless. Even the curly braces are optional if you have only one statement to execute within the body of the conditional block. The following code example is legal:

if (x > 3) // bad practice, but can be seen on the exam
y = 2;
z += 8;
a = y + x;

Sun considers it good practice to enclose blocks within curly braces, even if there’s only one statement in the block. Be careful with code like the above, because you might think it should read as

“If x is greater than 3, then set y to 2, z to z + 8, and a to y + x.”

But the last two lines are going to execute no matter what! They aren’t part of the conditional flow. You might find it even more misleading if the code were indented as follows:
if (x > 3)
y = 2;
z += 8;
a = y + x;

You might have a need to nest if-else statements. You can set up an if-else statement to test for multiple conditions. The following example uses two conditions so that if the first test fails, we want to perform a second test before deciding what to do:

if (price < 300) {

buyItNow();

} else {

if (price < 400) {

getApprovalFromWife();

}

else {

dontBuyItNow();

}

}


This brings up the other if-else construct, the if, else if, else. The preceding code could be rewritten:

if (price < 300) {

buyItNow();

} else if (price < 400) {

getApprovalFromWife();

} else {

dontBuyItNow();

}


There are a couple of rules for using else and else if:

• You can have zero or one else for a given if, and it must come after any else ifs.

• You can have zero to many else ifs for a given if and they must come before the else.

• Once an else if succeeds, none of the remaining else ifs or elses will be executed.


Don’t get your hopes up about the exam questions being all nice and indented properly. Just remember this “Anything that can be made more confusing, will be”


Be prepared for questions that not only fail to indent nicely, but intentionally indent in a misleading way: Pay close attention for misdirection like the following:

if (exam.done())

if (exam.getScore() < 0.61)

System.out.println("Try again.");

else

System.out.println("Java master!");


Legal Expressions for if Statements

The expression in an if statement must be a boolean expression. Any expression that resolves to a boolean is fine, and some of the expressions can be complex. Assume doSomething() returns true,


int y = 5;

int x = 2;


if (((x > 3) && (y < 2)) | doSomething()) {

System.out.println("true");

}


which prints

true


You can read the preceding code as, “If both (x > 3) and (y < 2) are true, or if the result of doSomething() is true, then print true.” So basically, if just doSomething() alone is true, we’ll still get true. If doSomething() is false, though, then both (x > 3) and (y < 2) will have to be true in order to print true. The preceding code is even more complex if you leave off one set of parentheses as follows

int y = 5;

int x = 2;

if ((x > 3) && (y < 2) | doSomething()) {

System.out.println("true");

}


which now prints...nothing! Because the preceding code (with one less set of parentheses) evaluates as though you were saying, “If (x > 3) is true, and either (y < 2) or the result of doSomething() is true, then print true.” So if (x > 3) is not true, no point in looking at the rest of the expression.” Because of the short-circuit &&, the expression is evaluated as though there were parentheses around (y < 2) | doSomething(). In other words, it is evaluated as a single expression before the && and a single expression after the &&.

Remember that the only legal expression in an if test is a boolean


Below are some illegal expressions

if (trueInt)

if (trueInt == true)

if (1)

if (falseInt == false)

if (trueInt == 1)

if (falseInt == 0)


Exam Tip:
One common mistake programmers make (and that can be difficult to spot), is assigning a boolean variable when you meant to test a boolean variable. Look out for code like the following:
boolean boo = false;
if (boo = true) { }

You might think one of three things:
1. The code compiles and runs fine, and the if test fails because boo is false.
2. The code won’t compile because you’re using an assignment (=) rather than an equality test (==).
3. The code compiles and runs fine and the if test succeeds because boo is SET to true (rather than TESTED for true) in the if argument!
Well, number 3 is correct. Pointless, but correct. Given that the result of any assignment is the value of the variable after the assignment, the expression (boo = true) has a result of true. Hence, the if test succeeds. But the only variables that can be assigned (rather than tested against something else) are a boolean or a Boolean; all other assignments will result in something non-boolean, so they’re not legal, as in the following:
int x = 3;
if (x = 5) { } // Won't compile because x is not a boolean!

Because if tests require boolean expressions, you need to be really solid on both logical operators and if test syntax and semantics.

switch Statements

A way to simulate the use of multiple if statements is with the switch statement. Take a look at the following if-else code, and notice how confusing it can be to have nested if tests, even just a few levels deep:
int x = 3;
if(x == 1) {
System.out.println("x equals 1");
}
else if(x == 2) {
System.out.println("x equals 2");
}
else if(x == 3) {
System.out.println("x equals 3");
}
else {
System.out.println("No idea what x is");
}

Now let’s see the same functionality represented in a switch construct:
int x = 3;
switch (x) {
case 1:
System.out.println("x is equal to 1");
break;
case 2:
System.out.println("x is equal to 2");
break;
case 3:
System.out.println("x is equal to 3");
break;
default:
System.out.println("Still no idea what x is");
}

Note: The reason this switch statement emulates the nested ifs listed earlier is because of the break statements that were placed inside of the switch. In general, break statements are optional, and as we will see in a few pages, their inclusion or exclusion causes huge changes in how a switch statement will execute.

Legal Expressions for switch and case

The general form of the switch statement is:
switch (expression) {
case constant1: code block
case constant2: code block
default: code block
}

A switch’s expression must evaluate to a char, byte, short, int, or, as of Java 6, an enum. That means if you’re not using an enum, only variables and values that can be automatically promoted (in other words, implicitly cast) to an int are acceptable. You won’t be able to compile if you use anything else, including the remaining numeric types of long, float, and double.

A case constant must evaluate to the same type as the switch expression can use, with one additional—and big—constraint: the case constant must be a compile time constant! Since the case argument has to be resolved at compile time, that means you can use only a constant or final variable that is assigned a literal value. It is not enough to be final, it must be a compile time constant. For example:

final int a = 1;
final int b;
b = 2;
int x = 0;
switch (x) {
case a: // ok
case b: // compiler error

Also, the switch can only check for equality. This means that the other relational operators such as greater than are rendered unusable in a case. The following is an example of a valid expression using a method invocation in a switch statement. Note that for this code to be legal, the method being invoked on the object reference must return a value compatible with an int.

String s = "xyz";

switch (s.length()) {
case 1:
System.out.println("length is one");
break;
case 2:
System.out.println("length is two");
break;
case 3:
System.out.println("length is three");
break;
default:
System.out.println("no match");
}

One other rule you might not expect involves the question, “What happens if I switch on a variable smaller than an int?” Look at the following switch:
byte g = 2;
switch(g) {
case 23:
case 128:
}

This code won’t compile. Although the switch argument is legal—a byte is implicitly cast to an int—the second case argument (128) is too large for a byte, and the compiler knows it! Attempting to compile the preceding example gives you an error something like

Test.java:6: possible loss of precision
found : int
required: byte
case 128:
^

It’s also illegal to have more than one case label using the same value. For example, the following block of code won’t compile because it uses two cases with the same value of 80:
int temp = 90;

switch(temp) {
case 80 : System.out.println("80");
case 80 : System.out.println("80"); // won't compile!
case 90 : System.out.println("90");
default : System.out.println("default");
}

It is legal to leverage the power of boxing in a switch expression. For instance, the following is legal:

switch(new Integer(4)) {
case 4: System.out.println("boxing is OK");
}

Break and Fall-Through in switch Blocks

The most important thing to remember about the flow of execution through a switch statement is this:

case constants are evaluated from the top down, and the first case constant that matches the switch’s expression is the execution entry point.

In other words, once a case constant is matched, the JVM will execute the associated code block, and ALL subsequent code blocks (barring a break statement) too!

Again, when the program encounters the keyword break during the execution of a switch statement, execution will immediately move out of the switch block to the next statement after the switch. If break is omitted, the program just keeps executing the remaining case blocks until either a break is found or the switch statement ends. Examine the following code:

int x = 1;
switch(x) {
case 1: System.out.println("x is one");
case 2: System.out.println("x is two");
case 3: System.out.println("x is three");
}
System.out.println("out of the switch");

The code will print the following:
x is one
x is two
x is three
out of the switch

This combination occurs because the code didn’t hit a break statement; execution just kept dropping down through each case until the end. This dropping down is actually called “fall-through,” because of the way execution falls from one case to the next. Remember, the matching case is simply your entry point into the switch block! In other words, you must not think of it as, “Find the matching case, execute just that code, and get out.” That’s not how it works. If you do want that “just the matching code” behavior, you’ll insert a break into each case as follows:

int x = 1;
switch(x) {
case 1: {
System.out.println("x is one"); break;
}
case 2: {
System.out.println("x is two"); break;
}
case 3: {
System.out.println("x is two"); break;
}
}
System.out.println("out of the switch");

Running the preceding code, now that we’ve added the break statements, will print
x is one
out of the switch

and that’s it. We entered into the switch block at case 1. Because it matched the switch() argument, we got the println statement, then hit the break and jumped to the end of the switch.
The Default Case
When using a switch case block, there might be stuff you want to do by default irrespective of whether the conditions in the case blocks match. That is why we have the Default: block.

Ex:

int x = someNumberBetweenOneAndTen;

switch (x) {
case 2:
case 4:
case 6:
case 8:
case 10: {
System.out.println("x is an even number");
break;
}
default: System.out.println("x is an odd number");
}

Previous Chapter: Self Test - Chapters 22 to 26

Next Chapter: Chapter 28: Loops and Iterators

No comments:

Post a Comment

© 2013 by www.inheritingjava.blogspot.com. All rights reserved. No part of this blog or its contents may be reproduced or transmitted in any form or by any means, electronic, mechanical, photocopying, recording, or otherwise, without prior written permission of the Author.

ShareThis

Google+ Followers

Followers