3: Flow of Control

What We Will Cover


Illuminations

Questions from last class?

Homework Questions?

Homework Discussion Questions

  1. Did anyone have difficulty developing the conversion algorithm?
  2. How easy was it to use variables and arithmetic statements?
  3. How easy was it to use CheckStyle?

3.1: Making Decisions

Learner Outcomes

At the end of the lesson the student will be able to:

  • Formulate relational expressions to produce boolean values
  • Assemble complex boolean expressions
  • Generate if and if-else statements
  • Create multiway if-else statements
  • Recognize switch statements and conditional operators

3.1.1: Flow of Control

  • The term flow of control refers to the order in which a computer executes instructions
  • You can write any program with just three control-flow elements:
  1. Sequence - continue with the next instruction
    • Sequence is the default operation
    • A program automatically goes on to the next statement unless directed otherwise
  2. Selection - a choice between at least two options
    1. Either execute some instruction based on a condition
    2. Or continue with the next instruction

    Selection statements include:

    if
    if-else
    switch
    
  3. Repetition - a loop (repeat a block of code)
    At the end of the loop:
    • Either go back and repeat the block of code
    • Or continue with the next instruction after the block

    Repetition statements include:

    for
    do-while
    while
    
  • We will start with selection statements

3.1.2: The Value of a Relationship

  • To make a selection (or repetition statement) we need to code a test condition
  • The computer uses the test to decide whether or not to execute the statement
  • One way to code a test condition is to use a relational expression

Relational Expressions

  • In addition to arithmetical operations, all computers can compare numbers
  • Many decisions can be reduced to choosing between two numbers
  • For instance, did the user enter a number equal to the correct guess
  • Expressions that compare numbers are called relational expressions
  • Simple relational expressions have one relational operator comparing two operands:

    relational expression

  • You can see the relational operators of C++ in the following table

Relational Operators

Math Name Java Examples   Result Notes
= Equal to == 5 == 10
2 == 2
false
true
Do not confuse with = which is assignment.
Not equal to != 5 != 10
2 != 2
true
false
The ! is like the line that "crosses through" the equal sign.
< Less than < 5 < 10
5 < 5
5 < 2
true
false
false
Less than or equal to <= 5 <= 10
5 <= 5
5 <= 2
true
true
false
Be careful not to write =<. Code the symbols in the order people normally say them.
> Greater than > 5 > 10
5 > 5
5 > 2
false
false
true
Greater than or equal to >= 5 >= 10
5 >= 5
5 >= 2
false
true
true
Be careful not to write =>. Code the symbols in the order people normally say them.

Comparing Characters

  • Character data can be evaluated using relational operators as well
  • Comparing characters works because Java stores characters as numbers using Unicode
  • Unicode is a 16-bit numerical code for letters and symbols covering most of the world's writing systems
  • Letters nearer to the start of the alphabet have lower numerical values
  • For comparisons, Java takes the 16-bit numerical code and converts it to an int
  • Thus you can use a numerical comparison to decide the alphabetical order of characters

Example Program Comparing Characters

1
2
3
4
5
6
7
8
9
10
11
12
public class CompareChar {
    public static void main(String[] args) {
        System.out.println("'A' == 'A': " + ('A' == 'A'));
        System.out.println("'A' != 'A': " + ('A' != 'A'));
        System.out.println("'A' < 'B': " + ('A' < 'B'));
        System.out.println("'C' < 'B': " + ('C' < 'B'));
        System.out.println("'A' <= 'X': " + ('A' <= 'X'));
        System.out.println("'7' > 'A': " + ('7' > 'A'));
        System.out.println("'a' > 'A': " + ('a' > 'A'));
        System.out.println("'a' < 'X': " + ('a' < 'X'));
    }
}

3.1.3: Using the if-else Statement

  • The if-else statement chooses between two alternatives based on a test condition
  • The condition must evaluate to either true or false
  • If a condition is true
    • then do this
  • Otherwise it is false
    • so do something else
  • Syntax:
    if (test) {
       statements1
    } else {
       statements2
    }
    
  • Where:
    • test: the test condition to evaluate
    • statements1: the statements to execute if the test evaluates to true
    • statements2: the statements to execute if the test evaluates to false
  • For clarity, if and else are written on different lines than the statements and the statements are indented
  • Note that the else clause can be omitted entirely
    if (test) {
       statements1
    }
    
  • The following examples shows and if-else statement

Example of an If-Else Statement

import java.util.Scanner;

public class GradeApp {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);

        System.out.print("I show pass or fail based"
            + " on a score.\nEnter your score: ");
        int score = input.nextInt();

        if (score >= 60) {
            System.out.println("You passed!");
        } else {
            System.out.println("Sorry, you failed.");
        }
    }
}

About those Curly Braces

  • Technically, the if and else affects only the single statement that follows
  • We can use curly braces to make the single statement into a block of statements
  • This allows us to put any number of statements within the block
  • Though curly braces are not always required, the best practice is to include them

Programming Style: Placement of Braces

  • Different practices for placing curly braces in a compound statement
  • Many programmers prefer placing curly braces on separate lines
  • However, Java standard is to place the opening brace on the same line as shown above
  • In practice, you should use the style dictated by your company's policy
    • Or your professor's instructions
  • For the acceptable styles for this course see: Curly Braces

3.1.4: Nested if Statements

  • You can include if statements within other if statements
  • Each inner if statement is evaluated only if outer condition is met

Example of Nested if Statements

import java.util.Scanner;

public class GradeApp2 {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);

        System.out.print("I show a grade based on a"
            + " score.\nEnter your score: ");
        int score = input.nextInt();

        if (score >= 60) {
            if (score >= 70) {
                if (score >= 80) {
                    if (score >= 90) {
                        System.out.println("A");
                    } else {
                        System.out.println("B");
                    }
                } else {
                    System.out.println("C");
                }
            } else {
                System.out.println("D");
            }
        } else {
            System.out.println("Sorry, you got an F");
        }
    }
}
  • Nested conditionals can be confusing if too deep
  • Rule of thumb: no more than three deep

"Dangling Else" Problem

  • A potential problem with nested if statements is the dangling else problem
  • if (test1)
        if (test2)
            Statement1;
    else
        Statement2;
    
  • Indentation is misleading and should be:
  • if (test1)
        if (test2)
            Statement1;
        else
            Statement2;
    
  • Common mistake is not matching the else with the correct if
  • Rule: else always matches the last unmatched if
  • Avoid this confusion by using both curly braces and indentation to show the grouping
  • if (test1) {
        if (test2) {
            Statement1;
        } else {
            Statement2;
        }
    }
    

3.1.5: Testing Multiple Conditions

  • One reason to use nested if statements is for testing multiple conditions
  • However, an often better way is to combine conditions using logical (conditional) operators
  • Logical operators allow you to consider multiple cases and to change conditions

AND Operation

  • One such operator is "and", which is spelled && in Java
  • For example, the following is true if score >= 80 and score < 90
  • (score >= 80) && (score < 90)
  • When two boolean expressions are connected using && the whole expression is true only if both conditions are true
  • However, if either condition is false the whole expression is false

OR Operation

  • Also, you can combine two boolean expressions using the "or" operator, spelled || in Java
  • For example, the following is true if score < 0 or score > 100
  • (score < 0) || (score > 100)
  • When two boolean expressions are connected using || the whole expression is true if either condition is true
  • Only if both conditions are false is the entire expression false

NOT Operation

  • Additionally, you can negate any expression using ! operator
  • To use it, place the entire expression in parenthesis and place the ! operator in front
  • For example, to get a age between 0 and 100, we can negate our previous example:
  • !((score < 0) || (score > 100))

Example Using Logical operators

import java.util.Scanner;

public class GradeApp3 {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);

        System.out.print("I show a grade based on a"
            + " score.\nEnter your score: ");
        int score = input.nextInt();

        if (score >= 90 && score < 100) {
            System.out.println("You got an A");
        }
        if (score >= 80 && score < 90) {
            System.out.println("You got a B");
        }
        if (score >= 70 && score < 80) {
            System.out.println("You got a C");
        }
        if (score >= 60 && score < 70) {
            System.out.println("You got a D");
        }
        if (score >= 0 && score < 60) {
            System.out.println("Sorry, you got an F");
        }

        if (score < 0 || score > 100) {
            System.out.println("Invalid score");
        }
    }
}

Truth Table for ! Operator

If expr is... Then ! expr is... Example Result
true false !true false
false true !(5 < 2) true

Truth Table for && Operator

If expr1 is... And expr2 is... Then expr1 && expr2 is... Example Result
true true true 5 < 10 && 5 > 2 true
true false false 5 < 10 && 5 < 2 false
false true false 5 > 10 && 5 > 2 false
false false false 5 > 10 && 5 < 2 false

Truth Table for || Operator

If expr1 is... || expr2 is... Then expr1 || expr2 is... Example Result
true true true 5 < 10 || 5 > 2 true
true false true 5 < 10 || 5 < 2 true
false true true 5 > 10 || 5 > 2 true
false false false 5 > 10 || 5 < 2 false

More Information

3.1.6: Using Multiway else-if Statements

  • Sometimes the order of the tests is important
  • When order is important, a useful construction is an if ... else-if statement
    • An if statement is nested inside an else clause
  • Test conditions are checked sequentially until finding the first true condition
  • Once found, the program executes the code within the associated block and skips the remainder of the if-else chain
  • You can code a final else clause as a default condition
  • If none of the other conditions are true then execute the default code
  • Note that if the order of tests were reversed in the following code we would get the wrong results

Example of a Multiway if-else-if Chain

import java.util.Scanner;

public class GradeApp4 {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);

        System.out.print("I show a grade based on a"
            + " score.\nEnter your score: ");
        int score = input.nextInt();

        if (score >= 90) {
            System.out.println("You got an A");
        } else if (score >= 80) {
            System.out.println("You got a B");
        } else if (score >= 70) {
            System.out.println("You got a C");
        } else if (score >= 60) {
            System.out.println("You got a D");
        } else {
            System.out.println("Sorry, you got an F");
        }
    }
}
  • Note the formatting of if-else-if statements
  • The else clauses are aligned rather than indented
  • This stops each else-if from creeping to the right

3.1.7: switch Statements

  • The switch statement gives us an alternative to an if-else-if chain
  • Executes a section of code depending on value of a variable
  • The general syntax is:
    switch (integerExpression) {
       case label1:
          statements
          break;
       case label2:
          statements
          break;
       ...
       case labeln:
          statements
          break;
       default:
          statements
    }
    
  • Where:
    • integerExpression: an arithmetic expression that resolves to an integer number
    • labelx: a numeric constant
    • statements: the statements to execute when the condition is met
  • Any number of case labels can be placed in any order
  • Any value that does not match starts executing with the statement after default
  • Execution continues until the end of the switch statement or a break statement
  • The break statement causes an immediate exit from the switch statement
  • Just as case identifies possible starting points, break determines end points

Example of a switch Statement:

import java.util.Scanner;

public class GradeApp5 {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);

        System.out.print("I show a grade based on a"
            + " score.\nEnter your score: ");
        int score = input.nextInt();

        switch (score / 10) {
            case 10:
            case 9:
                System.out.println("You got an A");
                break;
            case 8:
                System.out.println("You got a B");
                break;
            case 7:
                System.out.println("You got a C");
                break;
            case 6:
                System.out.println("You got a D");
                break;
            default:
                System.out.println("Sorry, an F");
        }
    }
}
  • Notice that each case other than the last contains a break statement
  • Ensures that the switch statement is exited after a matching case is found

When to Use switch Statements

  • You can only use switch statements with integer expressions and each case must be a constant
  • In addition, switch statements only work for exact matches (==)
  • Thus, switch statements are inherently less useful than if-else statements
  • Also, the syntax is no clearer than if-else statements
  • Note that there is a reason for the limitations of the switch statement
  • Many years ago a compiler could generate more efficient code (using jump tables or binary searches) only within the limitations of the switch statement
  • However, modern compilers are quite capable of optimizing if-else statements to the same degree
  • Thus, we have no reason to ever use a switch statement, depending on your compiler
  • On the other hand, there are reasons to avoid using a switch statement
  • Every branch of the switch statement must be terminated by a break statement
  • If the break statement is missing, the program falls through and executes the next case without testing
  • There are rare uses for this fall through behavior, such as printing the words for the song, The Twelve Days of Christmas
  • However, according to a study by Peter van der Linden, reported in his book, Expert C Programming, p. 38, the falling through behavior is needed less than 3% of the time
  • Thus, the default behavior is wrong 97% of the time
  • Forgetting to type the break statement is a very common error and the source of many bugs
  • So one has to ask oneself, "Why use an inferior programming statement that causes more problems than it solves?"

3.1.8: Conditional Ternary Operators

  • Another conditional statement is the conditional ternary operator
  • Provides a compact if-else structure
  • Syntax:
  • operand1 ? operand2 : operand3;
    
  • First operand must be a conditional expression that evaluates to true or false
  • If operand1 is true, return operand2; otherwise return operand3.

Example Comparing if-else with a Conditional Ternary Operator

  • Following is an if-else statement
  • if (n1 > n2) {
        max = n1;
    } else {
        max = n2;
    }
    
  • Equivalent using the conditional operator
  • max = (n1 > n2) ? n1 : n2;
    

When to Use Conditional (Ternary) Operators

  • Shorthand style rarely used in professional programing
  • Almost always clearer to use if-else statements
  • You can use it for the Java version of the Obfuscated C Code Contest

3.1.9: Summary

  • Selection is a choice between at least two options
  • Selection statements include:
  • if
    if-else
    switch
    
  • Single statements are expanded into compound statements using blocks
  • Test conditions can be created using relational expressions such as:
  • ==  !=  <   <=  >   >=
  • All relational expression evaluate to a boolean value: true or false
  • You can build complex conditions using logical operators
  • !   &&  ||
  • Nested if statements are legal but often confusing
    • Which if does an else match up with?
    • Use curly braces to clarify
    • Better yet, avoid using nested if statements
  • In contrast, multiway if ... else-if statements are very useful
    • if statements are nested in the else clause
    • Programs execute the code when the first condition matches
    • Often used to create menus
  • Java has an older style switch statement that is sometimes used
    • Inherently more limited than if-else statements
  • Also, Java has an older style conditional (ternary) operator that is rarely used

Check Yourself

  1. To what two values must a test condition always evaluate?
  2. True of false? An elegant way to choose among multiple alternatives is to nest if statements in an else clause.
  3. True of false? Order never matters in a sequence of if and else statements.
  4. True of false? A switch statement is a more powerful solution to multiple if-else-if statements.
  5. When does an AND (&&) of two or more conditions evaluate to true?
  6. When does an OR (||) of two or more conditions evaluate to false?
  7. What is the effect of the NOT (!) operator?
  8. True of false? A && B is the same as B && A for any Boolean conditions A and B.

Exercise 3.1

Take one minute to prepare an answer the following questions.

  1. What will happen if we attempt to compile and run the following code?
  2. class Dangling {
        public static void main(String[] args) {
            if (true)
            if (false)
            System.out.println("first");
            else
            System.out.println("second");
        }
    }
    
  3. Which of the following boolean expressions tests to see if x is between 2 and 15 (including 2 and 15)?
    1. (x <= 15 || x >= 2)
    2. (2 <= x || x <= 15)
    3. (x >= 2 && x <= 15)
    4. (2 <= x <= 15)

3.2: Repetition

Learner Outcomes

At the end of the lesson the student will be able to:

  • Use while and do-while Loops
  • Use for Loops
  • Generate the appropriate repetition structure for a Java program
  • Diagnose common looping problems

3.2.1: About Loops

  • Loops are used to repeat sections of code
  • General loop structure:
    • Initialization code
    • Loop condition -- evaluated during the loop
    • Loop body
  • Java has 3 looping statements
  • for
    while
    do-while
    
  • You can use any loop statement in place of another loop statement
    • With minor changes to your code

3.2.2: Using while and do-while Loops

  • The simplest loop statement is the while loop
  • while loop Syntax:
    while (test) {
       // statements to repeat
    }
    
  • Where test is the condition to evaluate at the start of each iteration
  • For example:
    countDown = 3;
    while (countDown > 0) {
        System.out.println("while");
        countDown = countDown - 1;
    }
    
  • Initialization statements usually precede the while loops
  • The test is checked at start of every loop iteration
  • Executes the loop body as long as test is true
  • Note that if the test is initially false then loop body never executes

do-while Loop

  • A variant of the while loop is the do-while loop
  • Syntax:
    do {
       // statements to repeat
    } while (test); //test condition
    
  • Where test is the condition to evaluate at the end of each iteration
  • The important difference between the while and do-while is when the loop test is checked
  • The body of the do-while loop always executes at least one time

Loop Examples

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
public class WhileDoWhile {
    public static void main(String[] args) {
        int countDown;

        System.out.println("First while loop:");
        countDown = 3;
        while (countDown > 0) {
            System.out.println("while");
            countDown = countDown - 1;
        }

        System.out.println("Second while loop:");
        countDown = 0;
        while (countDown > 0) {
            System.out.println("while");
            countDown = countDown - 1;
        }

        System.out.println("First do-while loop:");
        countDown = 3;
        do {
            System.out.println("do-while");
            countDown = countDown - 1;
        } while (countDown > 0);

        System.out.println("Second do-while loop:");
        countDown = 0;
        do {
            System.out.println("do-while");
            countDown = countDown - 1;
        } while (countDown > 0);
    }
}

3.2.3: Using for Loops

  • When we design a loop, we need to consider these things:
    1. Loop body -- which statements to repeat
    2. Test condition -- when to loop and when to stop
    3. Test update -- what must change to exit the loop
    4. Initialization code -- how to start the loop correctly
  • The for loop provides a syntax for these things in a compact form:
    for (initialization; test; update) {
        // statements to repeat
    }
    
  • Where:
    • initialization: statement to execute before the loop starts
    • test: condition to evaluate at the start of each iteration
    • update: statement to execute at the end of the loop
  • The for loop is primarily used for counting:
    for (int i = 0; i < 10; i++) {
        cout << i << endl;
    }
    
  • You can see an example below

for Loop Example

1
2
3
4
5
6
7
8
public class CounterFor {
    public static void main(String[] args) {
        int count;
        for (count = 0; count < 5; count++) {
            System.out.println(count);
        }
    }
}
  • Note that code is indented in the loop body for readability

Execution Sequence

  • The execution sequence of a for loop is:
    1. When for statement reached -- initialization executes
    2. If the test condition evaluates to true:
      1. Execute statements in the body of the loop
      2. When the end of loop body is reached, execute the update statement
      3. Return to step 2
    3. Otherwise, the loop is finished so continue with statements after the loop

Which Loop Statement to Use

  • Generally, we use for statements when counting with a loop
  • For non-counting loops where we want to ensure at least one iteration, use a do-while statement
  • Otherwise, use the simpler while statement
  • Notice that all loops have the same test condition
  • This makes it possible always to convert one looping statement into another with some adjustment to other code
  • Thus, choosing which loop statement to use is more a matter of elegance than a requirement

3.2.4: Nested Loops

  • More advance looping applications often have loops nested within other loops
  • For example, you may use a nested loop to print a table of values
  • The following example shows a simple table created with nested loops
  • Let's follow the execution sequence before checking the result

Example of Nested Loops

1
2
3
4
5
6
7
8
9
public class Nested {
   public static void main(String[] args) {
      for (int outer = 1; outer < 4; outer++) {
         for (int inner = 1; inner < 4; inner++) {
            System.out.println(outer + " " + inner);
         }
      }
   }
}

Tracing the Variables

  • Below is a trace of the variables for the inner and outer loops
  • Note that the outer loop changes only after the inner loop is finished
Memory  Screen 
 int outer   int inner   
  1   1 1 1
    2 1 2
    3 (end of loop)   1 3
  2   1 2 1
    2 2 2
    3 (end of loop)   2 3
  3 (end of loop)     1 3 1
    2 3 2
    3 (end of loop)   3 3

3.2.5: Using break and continue Statements

  • The break statement is an integral part of the switch statement
  • Another use the break statement is to terminate loops
  • The continue statement is similar except that instead of exiting a loop, it jumps to the end of the loop
  • The causes the loop to skip the rest of the current iteration and start the next iteration
  • The following code shows an example of break and continue
  • Note that using break and continue statements with loops is generally consider poor programming style
  • The reason we are reviewing them is to see what NOT to do

Example Using break and continue Statements

public class BreakContinue {
    public static void main(String[] args) {
        int count = 0;
        while (true) {
            count++;
            if (count == 3) continue;
            System.out.println(count);
            if (count >= 5) break;
        }
        System.out.println("After count= " + count);
    }
}

When to Use break and continue Statements

  • Use break statements in switch statements only
  • For this course, never use break or continue statements in looping constructs:
    • Poor coding habit
    • Loops should only have one exit point
  • Note that there is an interesting story about a major problem caused by the use of break statements
  • On January 15, 1990, a misused break statement caused an AT&T 4ESS telephone switch to fail
  • The failure propagated through the entire U.S. network, rendering it nearly unusable for about nine hours
  • A programmer had used a break to terminate an if statement
  • However, break cannot be used to break out of an if
  • Therefore the program execution broke out of the enclosing switch statement instead
  • Perhaps more importantly, using a break statement makes it difficult to prove program correctness in critical software

3.2.6: Looping Patterns

  • Let us look at some ways loops are commonly used

Counter-Controlled Loops

  • Use this pattern when you know how many times to repeat a section of code
  • The key is a variable called the counter
    1. Initialize the counter variable to 0
    2. Set the loop condition to check for the maximum count
    3. Adjust the counter variable inside the body of the loop
  • We saw this in our previous example:
public class CounterFor {
    public static void main(String[] args) {
        int count;
        for (count = 0; count < 5; count++) {
            System.out.println(count);
        }
    }
}
  • A variation is to start at the maximum value and count down

Ask Before Repeating

  • Another pattern is to ask the user whether or not to repeat a loop
  • Often used to allow the user to control the flow of a program
  • For example:
import java.util.Scanner;

public class SumAskFirst {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);

        System.out.println("I sum numbers");
        double total = 0.0; // Loop initialization
        do {
            System.out.print("Enter a number: ");
            double num = input.nextDouble();
            total += num;
            System.out.print("Another number? (y/n): ");
        } while (input.next().charAt(0) == 'y');
        System.out.println("Total: " + total);
    }
}
  • For a long list, this approach can be tiring
  • Often time, a better solution is to use a sentinel-controlled loop

Sentinel-Controlled Loops

  • Use a sentinel when you can use input data to tell the program when to stop
  • Looping continues as long as the data value read is not a sentinel value
  • When a sentinel value is read, the loop exits
  • For example:
import java.util.Scanner;

public class SumSentinel {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);

        System.out.println("I sum numbers.\n"
            + "Enter 0 to exit");
        double total = 0.0; // Loop initialization
        double num = 0.0;
        do {
            System.out.print("Enter a number: ");
            num = input.nextDouble();
            total += num;
        } while (num != 0);
        System.out.println("Total: " + total);
    }
}
  • num is the sentinel variable
  • The sentinel value is the number: 0
  • The program ends when the user enters the sentinel value

3.2.7: Common Loop Pitfalls

Infinite Loops

  • Common error: unintentional infinite loop
  • For example, what is wrong with the following code?
  • 1
    2
    3
    4
    5
    6
    7
    8
    
    public class InfiniteLoop1 {
        public static void main(String[] args) {
            int count = 0;
            while (count < 10) {
                System.out.println(count); // trace
            }
        }
    }
    

Empty Statements

  • Remember that statements are terminated by a semicolon
  • Is the following a legal statement?
  • ;
  • Known as an empty or null statement
  • Empty statements are a common source of infinite loops
  • For example, what is wrong with the following code?
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    
    public class InfiniteLoop2 {
        public static void main(String[] args) {
            int count = 0;
            while (count < 10); {
                System.out.println(count); // trace
                count++;
            }
        }
    }
    

Off-By-One Errors

  • A common looping problem is the off-by-one error
  • Often a less-than is confused with a less-than-or-equal-to
  • For example, to sum the even numbers between one and 10, what is wrong with:
  • 1
    2
    3
    4
    5
    6
    7
    8
    9
    
    public class OffByOne {
        public static void main(String[] args) {
            int sum = 0;
            for (int i = 1; i < 10; i += 2) {
                sum += i;
            }
            System.out.println(sum);
        }
    }
    

Tracing Variables

  • One good way to discover loop errors is to trace key variables
  • Tracing variables means watching them change as the program executes
  • You can insert temporary output statements in your program
  • For instance, we had trace statements in the infinite loop examples above
  • What variables would we trace for the off-by-one error example?

3.2.8: Summary

  • Loops are used to repeat sections of code
  • General loop structure:
    • Initialization code
    • Loop condition -- evaluated during the loop
    • Loop body
  • Several common uses of a loop
    • Counter-controlled loops
    • Ask Before Repeating
    • Sentinel-controlled loops
  • It is legal code to have one loop nested inside another
  • Several problems that can occur when using a loop
    • infinite loops: loops that "never" stop
    • Off-by-one errors: start or stop at a wrong value
  • Thoroughly test loops -- especially at the boundaries of the loop test
  • Tracing variables is a good way to discover loop errors

Exercise 3.2

Take one minute to prepare an answer the following:

for loops and while loops can be used interchangeably. Convert the following code from a for-loop to a while-loop.

public class CounterFor {
    public static void main(String[] args) {
        int count;
        for (count = 0; count < 5; count++) {
            System.out.println(count);
        }
    }
}

3.3: Strings

Learner Outcomes

At the end of the lesson the student will be able to:

  • Use Strings in Java programs
  • Convert Strings to primitive types
  • Convert other types to Strings
  • Parse Strings

3.3.1: About Strings and Characters

  • Recall that a string is a sequence of individual characters that are numbered
  • The following diagram shows how the string Hello is stored

    Character positions in a string

  • Note that the first character of a string is position 0, not 1

String Variables are Objects

  • String data types are actually objects in Java
  • A String variable does not store the characters of the string
  • Instead, a String variable stores the location of a String object in memory
  • For instance:
  • String message;
    message = new String("Hello");
    
  • The first line declares a reference variable of type String
  • The second line creates a String object in memory and assigns the object location to the reference variable
  • The keyword new creates the storage for an object and returns its memory location
  • String objects and characters

  • You can combine the two steps into one line:
  • String message = new String("Hello");
  • When you use a String variable, like in the statement above, the value of the variable is not displayed
  • Instead, Java finds the location of the object and uses the object itself
    • This is known as dereferencing the variable
    • Unlike some languages, Java automatically dereferences variables when needed
  • Thus, when we write a statement like:
  • System.out.println(message);
  • The content of the object referred to by the String variable gets displayed
  • It is common to refer to a reference variable as if it contains a value
    • This is a convenient mental shorthand
  • However, it is important to understand that this is technically incorrect
  • A reference variable always contains the memory address of where an object is stored
  • We will see a consequence of reference variables shortly

3.3.2: String Methods

  • Because a String is a class type, it has methods associated with it
  • The syntax for calling a method of a String object is:
    stringName.methodName(arguments)
    
  • Where:
    • stringName: the name of the string variable
    • methodName: the name of the method
    • arguments: the input values, if any
  • Once you create a string, you can use its methods
  • Some of the more commonly used methods are shown below
  • For a complete list, see the API for class String

Some Commonly-Used Methods of Class String

Method Description
length() Returns the number of characters in the string as an int.
charAt(index) Returns a char at the specified index.
equals(other) Compares two strings for equality and returns true if they are equal and false otherwise.
equalsIgnoreCase(other) Compares two strings for equality, ignoring upper or lower case, and returns true if they are equal and false otherwise.
indexOf(text) Returns the index of the first occurrence of text in the string or -1 if not present.
substring(begin, end) Returns a new string with all the characters from begin up to, but not including, end.
toLowerCase() Returns a new string with all characters in lowercase.
toUpperCase() Returns a new string with all characters in uppercase.
trim() Returns a new string with all whitespace removed from the beginning and end.

Example Using Some String Methods

1
2
3
4
5
6
7
8
9
10
11
12
13
14
public class StringMethods {
    public static void main(String[] args) {
        String greeting = "Hello, World!\n";
        System.out.print(greeting);
        System.out.println(greeting.length());
        String sub = greeting.substring(0, 5);
        System.out.println(sub); // prints "Hello"
        System.out.print(greeting.toLowerCase());
        System.out.print(greeting.toUpperCase());
        for (int i = 0; i < greeting.length(); i++) {
            System.out.println(greeting.charAt(i));
        }
    }
}

Character Sequence

  • Notice that strings have a sequence: there is a first character, second character, etc.
  • Each character is assigned an index number, starting with 0 and ending with length() - 1:
    H e l l o , W o r l d !
    0 1 2 3 4 5 6 7 8 9 10 11 12
  • The charAt() method uses this index in the example:
    String greeting = "Hello, World!";
    for (int i = 0; i < greeting.length(); i++) {
        System.out.println(greeting.charAt(i));
    }
    
  • The substring(begin, end) method also uses index numbers to select part of a string:
    String greeting = "Hello, World!";
    String sub = greeting.substring(0, 5);
    System.out.println(sub); // prints "Hello"
    
  • The begin argument starts with the index number of the first character
  • The end argument is always one past the last character to extract
    H e l l o , W o r l d !
    0 1 2 3 4 5 6 7 8 9 10 11 12
  • As another example, we can extract "World" using:
    String w = greeting.substr(7, 12);
    System.out.println(w);
    
  • Which extracts the characters highlighted in the following diagram:
    H e l l o , W o r l d !
    0 1 2 3 4 5 6 7 8 9 10 11 12
  • Note that you get a Runtime Exception (error) if you try to select an index lower than 0 or beyond the end of a string
  • For example, the code:
    char bogus = greeting.charAt(15);
    
    produces an error like:

    Exception in thread "main" java.lang.StringIndexOutOfBoundsException: String index out of range: 15
            at java.lang.String.charAt(String.java:687)
            at StringMethods.main(StringMethods.java:13)

3.3.3: Comparing Strings

  • "==" does not act like you may think for String objects
  • "==" tests to see if the address of the two objects are the same
  • The reason is that the two strings, s1 and s2, refer to different objects
  • Two string objects in memory

  • You must use the equals() method to test if the String contents are equal
    • Use equals() when case matters
    • Use equalsIgnoreCase() when case does NOT matter
  • The following example shows the result of using == and the methods equals() and equalsIgnoreCase()

Example of Comparing Strings

import java.util.*;

public class StringComparer {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);

        System.out.print("Enter string 1: ");
        String s1 = input.next();
        System.out.print("Enter string 2: ");
        String s2 = input.next();

        System.out.println(s1 == s2);
        System.out.println(s1.equals(s2));
        System.out.println(s1.equalsIgnoreCase(s2));
    }
}

More Information

3.3.4: Converting Strings

  • Recall that we can easily convert other data types, like int or double, to strings using the + operator
  • If an expression starts with a String type then the compiler converts other types to String automatically
  • For example:
    System.out.println("The result is: " + 7 + 2);
    
  • Produces the output:
    The result is: 72
    
  • If you want to add 7 + 2, you need to use parenthesis:
    System.out.println("The result is: " + (7 + 2));
    

Explicit Conversions

  • We often need to convert between String to numeric types
  • Java provides a very useful set of methods for converting between strings and primitive types
  • These methods are located in wrapper classes

    Wrapper class: a class that provides object representation and methods for primitive types.

  • Each of the eight Java primitive types have a wrapper class
  • Each of the classes has conversion methods we can use to convert between a string and the primitive type:

    String conversions

  • The following tables list some commonly used conversion methods

Converting Strings to Primitive Values

Return Type Conversion Method Example
int Integer.parseInt(String) Integer.parseInt("1234")
long Long.parseLong(String) Long.parseLong("123456")
float Float.parseFloat(String) Float.parseFloat("123.45")
double Double.parseDouble(String) Double.parseDouble("123.45")

Converting Primitive Values to Strings

Type to Convert Conversion Method Example
int Integer.toString(int) Integer.toString(1234)
long Long.toString(long) Long.toString(123456)
float Float.toString(float) Float.toString(123.45)
double Double.toString(double) Double.toString(123.45)

3.3.5: StringBuffer Class

  • After we create a String object, its contents cannot change
  • The immutability of String objects is an important property that we will look at in more depth later
  • However, sometimes we may prefer to work with a mutable (non-constant) string
  • For this we use a StringBuffer instead of a String
  • Like a String object, a StringBuffer represents a sequence of characters
  • However, StringBuffer has mutator methods that can change its sequence of characters
  • For example:
    StringBuffer str = new StringBuffer("ABCDE");
    str.reverse();
    Sytem.out.println(str); // displays EDCBA
    
  • Note that StringBuffer does not return a new String object
  • Instead, it actually modifies the StringBuffer referred to by str

More Information

3.3.6: Parsing Strings

  • Sometimes we need to take long strings and divide them into smaller pieces
  • The smaller pieces of the string are refered to as tokens

    Token: a text element that consists of a sequence of characters such as a word or symbol.

  • The process of splitting a string into tokens is known as parsing:

    Parse: to split a string of information into its constituent parts.

  • Often strings are split into tokens using whitespace as a delimiter ("divider" character)
  • However, other delimiters are used as well
  • An easy to use technique for tokenizing strings is to use the StringTokenizer class
  • The following shows an example program using a StringTokenizer

Example Application Tokenizing a String Using StringTokenizer

import java.util.*;

class Tokenizer {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);
        System.out.print("Enter a string: ");
        String data = input.nextLine();

        final String DELIMITERS = " ,!.?";
        StringTokenizer st =
            new StringTokenizer(data, DELIMITERS);
        while (st.hasMoreTokens()) {
            System.out.println(st.nextToken());
        }
    }
}

Tokenizing with Scanner

  • Another way to tokenize a string is to use a Scanner
  • By default a Scanner will split strings on whitespace delimiters
  • If you want to split strings on other delimiters, you will need to use a regular expression
  • The following example shows an example program using a Scanner
  • Unfortunately, regular expressions are beyond the scope of this course
  • However, you can study them using the Java Tutorial listed in More Information below

Example Application Tokenizing a String Using Scanner

import java.util.*;

class Tokenizer2 {
    public static void main(String[] args) {
        Scanner input  = new Scanner(System.in);
        System.out.print("Enter a string: ");
        String data = input.nextLine();

        final String DELIMITERS = "\\s*\\W+\\s*";
        Scanner scanner = new Scanner(data);
        scanner.useDelimiter(DELIMITERS);
        while (scanner.hasNext()) {
            System.out.println(scanner.next());
        }
    }
}

More Information

3.3.7: Summary

  • Strings are a sequence of individual characters and the characters are numbered from 0 to length() - 1:

    Character positions in a string

  • Strings are objects and have methods associated with them
  • We looked at several of these methods including substr()
  • When you compare strings, you need to use the equals() method rather than ==
    string1.equals(string2)
  • You use equalsIgnoreCase() when case does not matter
  • You can convert between a string and a primitive type using one of the wrapper classes for primitive types
  • A String is immutable -- it cannot be changed after it is created
  • If you want to work with a mutable string, then you can use a StringBuffer object:
    StringBuffer str = new StringBuffer("ABCDE");
    str.reverse();
    Sytem.out.println(str); // displays EDCBA
    

Exercise 3.3

Take one minute to prepare an answer the following question:

  1. Write a program that deterines if a string entered by the user is a palindrome. A palindrome is a string that reads the same forwards and backwards. Ignore case in making the determination.

    For testing you can use, "Able was I ere I saw Elba", which is palindromic with respect to spacing.

  2. What could we do to ignore spacing and punctuation in our palindrome tester?

Wrap Up

Due Next:
A2-Metabolic Energy (2/25/09)
A3-Postal Bar Codes (3/4/09)

Home | Blackboard | Announcements | Schedule | Room Policies | Course Info
Help | FAQ's | HowTo's | Links
Last Updated: March 29 2009 @18:00:18