Unit 3: Programming: Conditionals
Topics covered in this unit
After completing this unit you will:
- know how to use the relational operators
<
,>
,==
,<=
,>=
,!=
- be able to use the logical operators
&&
,||
, and!
- be able to evaluate simple and complex Boolean expressions
- be able to create your own Boolean expressions to test for a variety of conditions
- be able to use a series of conditional statements to perform a "switch-style" analysis of an expression
- be able to use an
EPSILON
constant to compare two decimal values - know how to use
.equals()
and.compareTo()
method to compareString
values - be able to use DeMorgan's Law to simplify complex Boolean expressions
3.0. Overview
To be "Turing-complete," a programming language needs to be able to make decisions based on a condition: "if this is true, execute that block of code."
In this section we'll learn how to evaluate boolean expressions, and see how they can be used in a program to execute code conditionally.
Let's get started.
- Making Decisions; Boolean Expressions
- Comparing Numbers
- Comparing Strings
- Nested Decisions
- Boolean Issues
3.1.0. Overview - Making Decisions; Boolean Expressions
The if
and if-else
statements are the way for a program to make a decision: "if something is true, do these things; otherwise, do these other things."
3.1.1. The if
statement
Most computer programs need to make a decision at some point, taking one action or another based on a condition.
The if
statement
The if
statement consists of a condition (an expression that evaluates to either true
or false
) and an instruction or body of code to be executed in the event that the condition is true.
if ( Boolean expression ) statement1; statement2; if ( Boolean expression ) { statement1; statement2; } statement3;
Either way, after the if
statement is executed, the program's execution proceeds to the instruction following the statement.
Look at these snippets of code from a BarBouncer
program to see how this works. Note how we've indented the code to indicate that it is the body of the conditional statement that is to be executed. This isn't required by Java's syntax, but it's smart to do, and required by just about anyplace you'll be that uses Java, including this course.
// checking identity cards at the club // the value of age has already been entered int ageLimit = 21; if (age >= ageLimit) System.out.println("You can enter the club."); if (age < ageLimit) System.out.println("No admittance.");
These two sets of statements work fine, but it's more efficient and safer to write them using an if-else
statement.
3.1.2. The if-else
statement
The if-else
statement
The if-else
statement consists of a standard if
condition (with a body of code to be executed if the condition is true), followed by they keyword else
, and a corresponding body of code to be executed if the condition is false.
if ( Boolean expression ) { statement1; statement2; } else { statement3; statement4; } statement5;
So,
int ageLimit = 21; if (age >= ageLimit) System.out.println("You can enter the club."); else System.out.println("No admittance.");
Why is this "safer?" There's only a single comparison being made, as opposed to two separate comparisons (<=
and >
) that have to be coordinated.
If there's more than a single statement that's going to be executed as the body of an if
statement, it needs to be enclosed in curly braces { }
to make it a code block:
// checking identity cards at the club // the value of age has already been entered int ageLimit = 21; double entranceFee = 0; if (age >= ageLimit) { entranceFee = 10.00; // fee to be collected System.out.println("You can enter the club."); } else { entranceFee = -1; // error code indicating no entrance System.out.println("No admittance."); }
With this in mind, what's wrong with the following code?
if (age >= ageLimit) entranceFee = 10.00; // fee to be collected System.out.println("You can enter the club.");
Although it looks like System.out.println("You can enter the club.");
is part of the code block, it's not—it's just another statement in the program. When this runs, if age
is less than ageLimit
, the entranceFee
will not be set to $10, and the under-age person will be told to enter the club with a fee of $0. That's probably not what we wanted to do.
How should this snippet be written?
Recommendation (required in this class)
To make your code as clean and easy to understand as possible, always use curly braces to enclose code for if-else
statements, even if each block is only a single line:
int ageLimit = 21;
if (age >= ageLimit)
{
System.out.println("You can enter the club.");
}
else
{
System.out.println("No admittance.");
}
3.2.0. Comparing Numbers
How can we compare two numbers in our conditional programming?
3.2.1. Comparing Numbers—Integers
We'll often want to perform operations conditionally based on comparing two numerical values. With integer values, this is easily done using one of the six relational operators.
Relational Operators
The relational operators are used to compare two values to each other, and have a value of true
or false
, depending on the relationship.
== | "is equal to" |
< | "less than" |
> | "greater than" |
<= | "less than or equal to" |
>= | "greater than or equal to" |
!= | "not equal to" |
Example:
Scanner in = new Scanner(System.in); System.out.print("Enter your age: "); double age = in.nextDouble(); if (age >= 18) { System.out.println("You can vote!"); } else { System.out.println("You can't vote (yet)."); }
In addition to the relational operators that compare two values there are logical operators that allow you to form more complex logical relationships.
3.2.2. Logical Operators
Logical Operators
The logical operators produce a value of true
or false
based on evaluating boolean values.
&& | "and": true if both expressions are true |
|| | "or": true if either expression is true |
! | "not": true if the expression is false, and false if the expression is true |
AND Example:
if (age >= 13 && age < 20) System.out.println("You're a teenager.");
OR Example:
if (age < 0 || age > 110) System.out.println("I don't think I believe you.");
NOT Example:
System.out.print("Enter a positive number"); double value = in.nextDouble(); if (!(value > 0)) System.out.println("ERROR: Positive number expected");
You might notice that in the NOT example above we could have re-written the if
statement so that it doesn't require a negation:
if (value <= 0)...
While this is correct, and you may even choose to write it without the !
in there, there will be occasions where using the not operator will allow your code to make more sense, and be more readable to you and others.
3.2.3. Common Mistakes
Common mistakes: you can't say if (0 < age < 21)...
and you can't say if (roll == 7 || 11)...
Each individual Boolean expression has be complete:
if (0 < age && age < 21)....
if (roll == 7 || roll == 11)...
3.2.4. Comparing Numbers—Doubles
Comparing doubles can get tricky, as you already know. Look at this code segment:
double root = Math.sqrt(2); double square = root * root; if (square == 2.0) System.out.println("We're good!"); else System.out.println("Uh-oh... The square of root 2 is " + square + " ???!");
The output produced by this code is Uh-oh... The square of root 2 is 2.0000000000000004 ???!
So what happened?
The decimal values that are stored as binary numbers in the computer suffer from rounding errors during some calculations. Ultimately, when working with floating-point (double
) values, we're not trying to identify whether the values are exactly the same. We just need to find out if they are close enough to being equal, where "close enough" is determined by you and the needs of the program.
Here's a common strategy for comparing doubles, and one I use in evaluating your work: Define a constant EPSILON
that is equal to some really small value like 1E-10. When you compare two values that are double
s, then, you check to see if the two values are within EPSILON
of each other. If they are, that's close enough!
final double EPSILON = 1E-10; // defined at beginning of class // w/ instance variables if (Math.abs(x - y) <= EPSILON) { // x is approximately = y System.out.println("That's close enough for me!"); }
3.3.0. Comparing Strings
Because Strings
are objects, you can't use the same relational operators that you can use with numbers. (Actually, you can, but it just checks to see if your strings are the same object, not whether their values are the same. And we usually want to compare values.)
Since Strings
are objects, you might expect that the String
class has some methods that allow you to compare them. And it does!
if (string1.equals(string2))...
String
s are case-sensitive, so if you want to check the letters regardless of case:
if (string1.equalsIgnoreCase(string2))...
If you want to find out their dictionary order:
if (string1.compareTo(string2) < 0)...
How does that last statement work? If it evaluates to true
—if string1.compareTo(string2)
has a negative value—then string1
comes before string2
, lexicographically.
Try this
Which lines work in this code segment? Which ones don't?
[1] String school = "Polytechnic"; [2] String schoolShort = school.substring(0,4); [3] if (school == "Polytechnic") [4] System.out.println("Go, Panthers!"); [5] if (schoolShort == "Poly") [6] System.out.println("Go, " + schoolShort);
All of the lines "work"—there are no syntax errors in this code—but lines 3 and 5 almost certainly don't do what a novice would expect. The output from running this code would only be "Go, Panthers!" Why?
school
and "Polytechnic" refer to the same object—the String "Polytechnic"—but this is not what we intended: We want to compare the values of these String objects. In this, you output is misleading because we might think we'd done the comparison correctly.
In the second comparision, nothing is printed. That's because schoolShort
(which has a value of "Poly") and the String "Poly" are not the same object! Again, we probably wanted to check to see if the values of those two things are the same, not whether they refer to the same object. This example points to the problem.
Lines 3 and 5 should say:
[3] if (school.equals("Polytechnic")) [5] if (schoolShort.equals("Poly"))
Comparing Strings?
NEVER use ==
to compare two string values! Always use .equals()
or .compareTo()
3.4.0. Nested Decisions
Another practical construction is nested if-else
statements, when there are multiple levels of decisions that have to be made.
if ( Boolean expression ) { statement; if ( Boolean expression ) { statement; statement; } else { statement; } statement; } else { statement; statement; } statement;
Do this
Write a program that has the user enter their age, and print one of four responses, based on where their age falls: < 18, between 18 and 20 (inclusive), between 21 - 30, and > 30.
What some advice? Here's how not to do it:
Scanner in = new Scanner(System.in); System.out.print("Enter your age: "); double age = in.nextDouble(); if (age < 18) System.out.println("You can't vote yet..."); if (age >= 18 && age < 21) System.out.println("You can't buy alcohol yet..."); if (age >= 21 && age < 31) System.out.println("These are the best years of your life..."); if (age >= 31) System.out.println("No, THESE are the best years of your life!");
The reason this is the wrong approach is that it involves multiple individual comparisons which are both harder to maintain as a coder and require more processing power/time from the computer. Additionally, it's easier to make a mistake here: if one statement checks for <21
and the other one checks for >21
, you've forgotten to take care of the case where the value == 21
.
A better approach is to use either a sequential series or a nested series of if-else
statements to help organize the decision process.
Scanner in = new Scanner(System.in); System.out.print("Enter your age: "); double age = in.nextDouble(); if (age < 18) System.out.println("You can't vote yet..."); else if (age < 21) System.out.println("You can't buy alcohol yet..."); else if (age <= 31) System.out.println("These are the best years of your life..."); else System.out.println("No, THESE are the best years of your life!");
Even better would be enclose these statement in curly braces:
Scanner in = new Scanner(System.in); System.out.print("Enter your age: "); double age = in.nextDouble(); if (age < 18) { System.out.println("You can't vote yet..."); } else if (age < 21) { System.out.println("You can't buy alcohol yet..."); } else if (age <= 31) { System.out.println("These are the best years of your life..."); } else { System.out.println("No, THESE are the best years of your life!"); }
Now try this one.
Decisions, decisions
When choosing a beverage, I sometimes like caffeinated drinks and sometimes non-caffeinated drinks. If I want caffeine and it's before noon, I'll have coffee, but if it's in the afternoon (anytime before 6pm) I'll have a coke. After 6pm I'll have tea. On the other hand, if I don't want caffeine, in the morning I like herbal tea, and in the afternoon I'll have water, and in the evening, I'll have herbal tea.
Develop a method selectBeverage()
that takes two parameters: the time of day (an int
, where 1800 = 6pm) and a String
(either "caffeinated" or "non-caffeinated"). The method should return a String
that indicates what beverage I should drink.
public String selectBeverage(int timeOfDay, String typeOfBeverage) { if (typeOfBeverage.equals("caffeinated")) { if (time < 1200)) { return "coffee"; } else if (time < 1800)) { return "coke"; } else { return "tea"; } } else { if (time < 1200)) { return "herbal tea"; } else if (time < 1800)) { return "water"; } else { return "herbal tea"; } } }
3.5.0. Boolean Issues
There are a number of common issues that can arise when programming with Boolean expressions.
3.5.1. "Dangling Else" Problem
The dangling else problem has to do with ambiguity about which if statement an else might be associated with. Consider this legal if-else
statement:
boolean adultStatus = false; // assume not adult if (age > 0) // Error trap if (age > 18) boolean adultStatus = true; else System.out.println("Error: your age must be greater than 0.");
The problem is that the else
statement, although it looks like it goes with the (age > 0)
condition because of the way it's typed, it actually goes with the (age > 18)
condition (an else
goes with the nearest if
). Here, an age < 0 won't get trapped, and an age of 17 will produce an "age must be greater than 0" error.
You fix the dangling else problem by using curly braces to correctly isolate the bodies of your if-else
statements.
boolean adultStatus = false; // assume not adult if (age > 0) // Error trap { if (age > 18) boolean adultStatus = true; } else { System.out.println("Error: your age must be greater than 0."); }
3.5.2. The null
value
It's possible for a variable to have a null
value, which simply means that the value hasn't been set. null
is different from 0, and different from "", and different from being undefined. You can check to see if a value has been set for any variable by comparing the variable using == null
:
if (middleName != null) System.out.println("Middle name is: " + middleName);
3.5.3. DeMorgan's Law
DeMorgan's Law is not a problem, but rather a strategy that you can use to help solve the problem of complex Boolean expressions.
DeMorgan's Law is concerned with simplifying Boolean expressions that include both the not operator (!
) and a Boolean operators. These can be confusing if they're awkwardly phrased, but you can use DeMorgan's Law to convert them to a different, more manageable, form.
DeMorgan's Law
Boolean expressions that include a not operator ( !
) and boolean operators and ( &&
in Java ) or or ( ||
in Java) can be simplified by using these conversions:
!(A && B) → (!A || !B)
and
!(A || B) → (!A && !B)
In each case, a not operation acting on a compound boolean expression is distributed over the expressions, and the comparator inside the parentheses is changed to its opposite, from or to and or from and to or.
It may also be useful for you to simplify not
expressions that have comparators.
!(age < 0) → age >= 0 !(cost >= 1000) → cost < 1000
Applying DeMorgan's Law - Playing a Game
The game condition below allows a user to take 3 turns to play a game, and let's them play as many times as they want. (Not all of the code is shown.) The boolean expression for the if
header is written a little confusingly. Rewrite it using DeMorgan's Law.
if ( !(done || turns >=3) ) { ... ... }
if ( !done && turns < 3 ) ...