AP Computer Science

Unit 3: Programming: Conditionals

Topics covered in this unit

After completing this unit you will:

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.

  1. Making Decisions; Boolean Expressions
  2. Comparing Numbers
  3. Comparing Strings
  4. Nested Decisions
  5. 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 doubles, 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))...

Strings 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);

Show/hide answers

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:

Show/hide wrong approach

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.

Show/hide correct approach

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.

Show/hide solution

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) )
{
    ...
    ...
}

Show/hide solution

if ( !done && turns < 3 ) ...