facebook google plus twitter
Webucator's Free Java Tutorial

Lesson: Comparisons and Flow Control Structures

Welcome to our free Java tutorial. This tutorial is based on Webucator's Introduction to Java Training course.

In this lesson, you will learn about the program's flow control.

Lesson Goals

  • Learn about boolean values and expressions.
  • Learn how to create complex boolean expressions using AND and OR logic.
  • Learn how to write flow control statements using if and if ... else structures.
  • Learn how to compare objects for equality and identity.
  • Write loops using while, do ... while, and for loop structures.

Boolean-valued Expressions

Java has a data type called boolean. Note the following:

  • Possible values are true or false.
  • boolean can be operated on with logical or bitwise operators, but cannot be treated as a 0 or 1 mathematically.
  • boolean can be specified as a parameter type or return value for a method.
  • A boolean value will print as true or false.

The result of a conditional expression (such as a comparison operation) is a boolean value. Note that:

  • Simple comparisons, such as greater than, equal to, etc., are allowed.
  • Values are returned from functions.
  • Complex combinations of simpler conditions are allowed.

Conditional expressions are used for program control. That means they provide the ability for a program to branch based on the runtime condition or iterate through a block of code until the condition is false.

Comparison Operators

This chart shows the comparison operators and the types of data they can be applied to.
Operator
Purpose (Operation Performed)
Types of Data
boolean
Numeric Primitives and char
Object References
== is equal to (note the two equals signs!) X X X
!= is not equal to X X X
> is greater than X
< is less than X
>= is greater than or equal to X
<= is less than or equal to X

Note that it is unwise to use == or != with floating-point data types, as they can be imprecise.

Comparing Objects

The operators == and != test if two references point to exactly the same object in memory – they test that the numeric values of the two references are the same. The equals(Object o) method compares the contents of two objects to see if they are the same (you can override this method for your classes to perform any test you want).

Conditional Expression Examples

The following are conditional expression examples.

  • a = 3; b = 4;
  • a == b will evaluate to false
  • a != b will evaluate to true
  • a > b will evaluate to false
  • a < b will evaluate to true
  • a >= b will evaluate to false
  • a <= b will evaluate to true
String s = "Hello";
String r = "Hel";
String t = r + "lo";

s == t will evaluate to false

s != t will evaluate to true (they are not the same object - they are two different objects that have the same contents)

s.equals(t) will evaluate to true

String s = "Hello";
String t = s;

s == t will evaluate to true

s != t will evaluate to false (they are the same object)

s.equals(t) will evaluate to true

Note: Java will intern a literal String that is used more than once in your code; that is, it will use the same location for all occurrences of that String

String s = "Hello";
String t = "Hello";

s == t will evaluate to true, because the String object storing "Hello" is stored only once, and both s and t reference that location

s != t will evaluate to false (they are the same object)

s.equals(t) will evaluate to true

Complex boolean Expressions

Java has operators for combining boolean values with AND and OR logic, and for negating a value (a NOT operation). Note that:

  • There are also bitwise operators for AND, OR, and bitwise inversion (changing all 1 bits to 0, and vice versa).
  • Since a boolean is stored as one bit, the bitwise AND and OR operators will give the same logical result, but will be applied differently (see below).
  • For some reason, the bitwise NOT cannot be applied to a boolean value.
Logical Bitwise
&& logical AND & bitwise AND
|| logical OR | bitwise OR
! logical NOT ~ bitwise NOT (inversion)

Examples:

Code Effect
Testing if a value falls within a range

( ( a >= 0 ) && ( a <= 10 ) )

is true if a is falls between 0 and 10; it must satisfy both conditions

Testing if a value falls outside a range

( ( a < 0 ) || ( a > 10 ) )

is true if a falls outside the range 0 to 10; it may be either below or above that range

( !( ( a >= 0 ) && ( a <= 10) ) )

inverts the test for a between 0 and 10; so a must be outside the range instead of inside it

The && and || operations are called short-circuiting, because if the first condition determines the final outcome, the second condition is not evaluated.

To force both conditions to be evaluated, use & and | for AND and OR, respectively.

Example: to test if a reference is null before calling a boolean valued method on it.

String s = request.getParameter("shipovernite");
if (s != null && s.equalsIgnoreCase("YES")) { . . . }

equalsIgnoreCase will be called only if the reference is not null.

The if Statement

if (condition) statement;

or

if (condition) {
	zero to many statements;
}

Causes "one thing" to occur when a specified condition is met.

The one thing may be a single statement or a block of statements in curly braces. The remainder of the code (following the if and the statement or block it owns) is then executed regardless of the result of the condition.

The conditional expression is placed within parentheses.

The following flowchart shows the path of execution:

If Statement Flowchart

if Statement Examples

Absolute Value

If we needed the absolute value of a number (a number that would always be positive):

Code Sample:

Java-Control/Demos/If1.java
public class If1 {
  public static void main(String[] args) {
    int a = 5;
    System.out.println("original number is: " + a);
    if ( a < 0 ) { a = -a; }
    System.out.println("absolute value is: " + a);
    a = -10;
    System.out.println("original number is: " + a);
    if ( a < 0 ) { a = -a; }
    System.out.println("absolute value is: " + a);
  }
}

Random Selection

Task: write a brief program which generates a random number between 0 and 1. Print out that the value is in the low, middle, or high third of that range (Math.random() will produce a double value from 0.0 up to but not including 1.0).

  • Although it is a bit inefficient, just do three separate tests: (note that Math.random() will produce a number greater than or equal to 0.0 and less than 1.0).

Code Sample:

Java-Control/Demos/If2.java
public class If2 {
  public static void main(String[] args) {
    double d = Math.random();
    System.out.print("The number " + d);
    if (d < 1.0/3.0) 
      System.out.println(" is low");
    if (d >= 1.0/3.0 && d < 2.0/3.0) 
      System.out.println(" is middle");
    if (d >= 2.0/3.0) 
      System.out.println(" is high");
  }
}

Game01: A Guessing Game

Duration: 15 to 20 minutes.

Write a program called Game that will ask the user to guess a number and compare their guess to a stored integer value between 1 and 100.

  1. Use a field called answer to store the expected answer.
  2. For now, just hard-code the stored value; we will create a random value later (your code will be easier to debug if you know the correct answer).
  3. Create a method called play() that holds the logic for the guessing. Use the KeyboardReader class to ask for a number between 1 and 100, read the result, and tell the user if they are too low, correct, or too high.
  4. Create a main method, have it create a new instance of Game and call play().

Solution:

Solutions/Game01/Game.java
import util.*;

public class Game {
	private int answer = 67;
	
	public void play() {
		int guess;
		guess = KeyboardReader.getPromptedInt("Enter a number 1 -100: ");
		if (guess < answer) System.out.println("Too low");
		if (guess > answer) System.out.println("Too high");
		if (guess == answer) System.out.println("Correct!");
	}
	
	public static void main(String[] args) {
		new Game().play();		
	}
}

Each of the the three possible cases is tested individually as shown below. All three tests will always be performed. In the next version, we will make the tests mutually exclusive, so that processing stops when one is true. we will use a more efficient approach.

if (guess < answer) System.out.println("Too low");
if (guess > answer) System.out.println("Too high");
if (guess == answer) System.out.println("Correct!");

Payroll-Control01: Modified Payroll

Duration: 10 to 15 minutes.

We will modify our payroll program to check the pay rate and department values.

  1. The Employee class should protect itself from bad data, so modify setPayRate and setDept to check the incoming data (and ignore it if it is less than 0).
  2. Test your program to see what happens with negative input values.
  3. The code using Employee should avoiding sending it bad data, so also change main in Payroll.java to check for acceptable pay rate and department values (print an error message for any negative entry, then just set that variable to 0 (for lack of anything better that we can do right now. Later we will find a way to ask the user for a new value instead)

Solution:

Solutions/Payroll-Control01/employees/Employee.java
package employees;

public class Employee {

---- C O D E   O M I T T E D ----
	public int getDept() { return dept; }

	public void setDept(int dept) {
		if (dept > 0) this.dept = dept;
	}


	public double getPayRate() { return payRate; }

	public void setPayRate(double payRate) {
		if (payRate >= 0) this.payRate = payRate;
	}


---- C O D E   O M I T T E D ----
}

The Employee class should protect itself from bad incoming data, so the setPayRate method simply ignores a value less than 0. A better practice, which we will add later, would be to throw an exception when an illegal value is passed in. Note the benefit of coding the constructors to use the setPayRate method; we do not have to go back and revise their code as well. (setDept has similar changes.)

Solution:

Solutions/Payroll-Control01/Payroll.java
import employees.*;
import util.*;

public class Payroll {	
	public static void main(String[] args) {
		Employee e1 = null, e2 = null, e3 = null;
		String fName = null;
		String lName = null;
		int dept = 0;
		double payRate = 0.0;

		fName = KeyboardReader.getPromptedString("Enter first name: ");
		lName = KeyboardReader.getPromptedString("Enter last name: ");
		dept = KeyboardReader.getPromptedInt("Enter department: ");
		if (dept <= 0) { 
			System.out.println("Department must be > 0");
			dept = 0;
		}
		payRate = KeyboardReader.getPromptedDouble("Enter payRate: ");
		if (payRate < 0.0) {
			System.out.println("Pay rate must be >= 0");
			payRate = 0.0;
		}
		e1 = new Employee(fName, lName, dept, payRate);
		System.out.println(e1.getPayInfo());


---- C O D E   O M I T T E D ----
	}
}

Even though Employee now protects itself from illegal incoming values, it should really be the responsibility of the progmmers for the classes using Employee to avoid sending it bad values. So, the payRate value is tested here as well. While this may seem to decrease efficiency (since, presumably, a non-object-oriented program might be able to avoid testing the value twice in a row), the maintainability aspects of OOP are still considered to outweigh the loss in efficiency.

Two Mutually Exclusive Branches

The if ... else Statement

if (condition) statement;
else if { block } statement;
else { block }

The if ... else Statement does "one thing" if a condition is true, and a different thing if it is false.

It is never the case that both things are done. The "one thing" may be a single statement or a block of statements in curly braces.

A statement executed in a branch may be any statement, including another if or if ... else statement.

If ... Else Statement Flowchart

This program tells you that you are a winner on average once out of every four tries.

Code Sample:

Java-Control/Demos/IfElse1.java
public class IfElse1 {
  public static void main(String[] args) {
    double d = Math.random();
    if (d < .25) System.out.println("You are a winner!");
    else System.out.println("Sorry, try again!");
  }
}

Nested if ... else Statements - Comparing a Number of Mutually Exclusive Options

You can nestif ... else statements, so that an if or else clause contains another test. Both the if and the else clause can contain any type of statement, including another if or if ... else.

You can test individual values or ranges of values. Once an if condition is true, the rest of the branches will be skipped. You could also use a sequence of if statements without the else clauses (but this doesn't by itself force the branches to be mutually exclusive).

Here is the low/middle/high example rewritten using if ... else

Code Sample:

Java-Control/Demos/IfElse2.java
public class IfElse2 {
  public static void main(String[] args) {
    double d = Math.random();
    System.out.print("The number " + d);
    if (d < 1.0/3.0) 
      System.out.println(" is low");
    else if (d < 2.0/3.0) 
      System.out.println(" is middle");
    else 
      System.out.println(" is high");
  }
}

Similarly, we do not test the high third at all. The original version worked because there was no chance that more than one message would print; that approach is slightly less efficient because all three tests will always be made. In the if ... else version, comparing stops once a match has been made.

Game02: A Revised Guessing Game

Duration: 10 to 15 minutes.
  1. Revise your number guessing program to use if . . . else logic (you can test for too low and too high, and put the message for correct in the final else branch).
  2. Once you have done that, here is a way to generate a random answer between 1 and 100:
    1. At the top:
      import java.util.*;
    2. Add a private field for a random number generator:
      private Random r = new Random();
    3. Then, you can initialize the answer field:
      answer = r.nextInt(100) + 1;
      the nextInt(int n) method generates a number greater than or equal to 0 and less than n, so r.nextInt(100) would range from 0 through 99; we need to add 1 to raise both ends of the range.
    4. You might want to print the expected correct answer to aid debugging.

Note that until we cover looping, there will be no way to truly "play" the game, since we have no way to preserve the value between runs.

Solution:

Solutions/Game02/Game.java
import util.*;
import java.util.*;

public class Game {
	private Random r = new Random();
	private int answer = r.nextInt(100) + 1;
	
	public void play() {
		System.out.println(answer);
		int guess;
		guess = KeyboardReader.getPromptedInt("Enter a number 1 -100: ");
		if (guess < answer) System.out.println("Too low");
		else if (guess > answer) System.out.println("Too high");
		else System.out.println("Correct!");
	}
	
	public static void main(String[] args) {
		new Game().play();		
	}
}

Comparing a Number of Mutually Exclusive options - The switch Statement

The switch Statement

A switch expression (usually a variable) is compared against a number of possible values. It is used when the options are each a single, constant value that is exactly comparable (called a case).

The switch expression must be a byte, char, short, or int. Cases may only be byte, char, short, or int values; in addition, their magnitude must be within the range of the switch expression data type and cannot be used with floating-point datatypes or long and cannot compare an option that is a range of values, unless it can be stated as a list of possible values, each treated as a separate case.

Cases are listed under the switch control statement, within curly braces, using the case keyword. Once a match is found, all executable statements below that point are executed, including those belonging to later cases; this allows stacking of multiple cases that use the same code.

The break; statement is used to jump out of the switch block, thus skipping executable steps that are not desired. The default case keyword catches all cases not matched above - note that the default case does not need to be the last thing in the switch. Note that technically speaking, the cases are labeled lines; the switch jumps to the first label whose value matches the switch expression.

Usage

switch ( expression ) {
	case constant1: 
		statements;
		break;
	case constant2:
		statements;
		break;
	. . .
	case constant3:
	case constant4:
		//do these following statements for case constant3 & constant4
		statements;
		break;
	. . .
	[default:
		statements;]
}

switch Statement Examples

 

Code Sample:

Java-Control/Demos/Switch1.java
import util.KeyboardReader;

public class Switch1 {
  public static void main(String[] args) {
    char c = 'X';
    c = KeyboardReader.getPromptedChar("Enter a letter a - d: ");
    switch (c) {
      case 'a':
      case 'A': System.out.println("A is for Aardvark");
                break;
      case 'b':
      case 'B': System.out.println("B is for Baboon");
                break;
      case 'c':
      case 'C': System.out.println("C is for Cat");
                break;
      case 'd':
      case 'D': System.out.println("D is for Dog");
                break;
      default:  System.out.println("Not a valid choice");
    }
  }
}

Three points to note:

  • Stacking of cases for uppercase and lowercase letters allows both possibilities.
  • break; statements used to separate code for different cases.
  • default: clause used to catch all other cases not explicitly handled.

Here is a revised version that moves the default to the top, so that a bad entry is flagged with an error message, but then treated as an 'A' - note that there is no break below the default case.

Code Sample:

Java-Control/Demos/Switch2.java
import util.KeyboardReader;

public class Switch2 {
  public static void main(String[] args) {
    char c = 'X';
    c = KeyboardReader.getPromptedChar("Enter a letter a - d: ");
    switch (c) {
      default:  System.out.println("Not a valid choice - assuming 'A'");
      case 'a':
      case 'A': System.out.println("A is for Aardvark");
                break;
      case 'b':
      case 'B': System.out.println("B is for Baboon");
                break;
      case 'c':
      case 'C': System.out.println("C is for Cat");
                break;
      case 'd':
      case 'D': System.out.println("D is for Dog");
                break;
    }
  }
}

Another example is taking advantage of the "fall-though" behavior without a break statement.

Code Sample:

Java-Control/Demos/Christmas.java
import util.KeyboardReader;

public class Christmas {
  public static void main(String[] args) {
    int day = KeyboardReader.getPromptedInt("What day of Christmas? ");
    System.out.println(
        "On the " + day + " day of Christmas, my true love gave to me:");
    switch (day) {
      case 12: System.out.println("Twelve drummers drumming,"); 
      case 11: System.out.println("Eleven pipers piping,"); 
      case 10: System.out.println("Ten lords a-leaping,"); 
      case 9: System.out.println("Nine ladies dancing,"); 
      case 8: System.out.println("Eight maids a-milking,"); 
      case 7: System.out.println("Seven swans a-swimming,"); 
      case 6: System.out.println("Six geese a-laying,"); 
      case 5: System.out.println("Five golden rings,"); 
      case 4: System.out.println("Four calling birds,"); 
      case 3: System.out.println("Three French hens,"); 
      case 2: System.out.println("Two turtle doves, and a "); 
      case 1: System.out.println("Partridge in a pear tree!");
    }
  }
}

Game03: Multiple Levels

Duration: 15 to 30 minutes.

What if we want to offer gamers multiple levels of difficulty in our game? We could make the random number multiplier a property of the Game class, and set a value into it with a constructor, after asking the user what level they'd like to play.

  1. Add a range field to the Game class (an int).
  2. Add a constuctor that accepts a level parameter; use a char.
  3. Within that constructor, use a switch to process the incoming level:
    • Uppercase or lowercase B means Beginner, set the range to 10.
    • I means Intermediate; set the range to 100.
    • A means Advanced; set the range to 1000.
    • Any other value results a Beginner game; after all, if they can't answer a simple question correctly, how could we expect them to handle something more advanced.
    • You could put the default option stacked above the "B" cases, so that you can print an error message and then fall through to the 'B' logic
  4. Use range as the parameter when you call the Random object's nextInt method (move the logic that creates the Random object and generates the answer to this constructor).
  5. Add a default constructor that calls this new constructor, passing 'I' for intermediate.
  6. The prompt given by the play method should now take the range into account.
  7. In the main method, ask the user for the level and call the new constructor with their response.

Solution:

Solutions/Game03/Game.java
import util.*;
import java.util.*;

public class Game {
	private Random r = new Random();
	private int answer;
	private int range = 10;
	
	public Game() {
		this('I');
	}
	
	public Game(char level) {
		switch (level) {
			default: 
				System.out.println("Invalid option: " +
						level + ", using Beginner");
			case 'b':
			case 'B':
				range = 10;	
				break;
			case 'i':
			case 'I': range = 100;
				break;
			case 'a':
			case 'A': range = 1000;
				break;
		}
		Random r = new Random();
		answer = r.nextInt(range) + 1;
		//System.out.println(answer);
	}
	
	public void play() {
		int guess;
		guess = 
			KeyboardReader.getPromptedInt("Enter a number 1 - " + range + ": ");
		if (guess < answer) System.out.println("Too low");
		else if (guess > answer) System.out.println("Too high");
		else System.out.println("Correct!");
	}
	
	public static void main(String[] args) {
		char level = KeyboardReader.getPromptedChar("What level (B, I, A)? ");
		new Game(level).play();		
	}
}

The switch tests for the three letters, stacking cases for uppercase and lowercase values. The default catches all other responses and falls through to the Beginner logic.

Comparing Objects

The operators == and != test if two references point to exactly the same object in memory; they test that the numeric values of the two references are the same.

The equals(Object o) method compares the contents of two objects to see if they are the same (you can override this method for your classes to perform any test you want).

When comparing two objects, the == operator compares the references and not the values of the objects. Similarly, != tests to see if the references point to two different objects (even if they happen to have the same internal values).

The Object class defines an equals(Object) method intended to compare the contents of the objects. The code written in the Object class simply compares the references using ==. This method is overridden for most API classes to do an appropriate comparison. For example, with String objects, the method compares the actual characters up to the end of the string. For classes you create, you would override this method to do whatever comparisons you deem appropriate.

Code Sample:

Java-Control/Demos/Rectangle.java
public class Rectangle {
  private int height;
  private int width;
  
  public Rectangle(int height, int width) {
    this.height = height;
    this.width = width;
  }
  
  public int getArea() {
    return height * width;
  }
  
  public boolean equals(Object other) {
    if (other instanceof Rectangle) {
      Rectangle otherRect = (Rectangle) other;
      return this.height == otherRect.height && 
             this.width == otherRect.width;
    }
    else return false;
  }
}

The equals method compares another object to this object.

This example is necessarily somewhat complicated. It involves a few concepts we haven't covered yet, including inheritance. Because the equals method inherited from Object, it takes a parameter which is an Object, we need to keep that that type for the parameter (technically, we don't need to do it by the rules of Java inheritance, but because other tools in the API are coded to pass an Object to this method). But, that means that someone could call this method and pass in something else, like a String.

So, the first thing we need to do is test that the parameter was actually a Rectangle. If so, we can work with it; if not, there is no way it could be equal, so we return false.

We will cover instanceof later, but, for now, we can assume it does what it implies: test that the object we received was an instance of Rectangle. Even after that test, in order to treat it as a Rectangle, we need to do a typecast to explicitly store it into a Rectangle variable (and again, we will cover object typecasting later). Once we have it in a Rectangle variable, we can check its height and width fields.

In yet another complication, if we write an equals method, we should really write a public int hashcode() method as well, since their meanings are interrelated; two elements that compare as equal should produce the same hashcode.

As an aside, note that private data in one instance of a class is visible to other instances of the same class; it is only otherclasses that cannot see the private elements.

Code Sample:

Java-Control/Demos/ObjectEquivalenceIdentity.java
class ObjectEquivalenceIdentity {
  
  public static void main(String[] args) {
    Rectangle r1 = new Rectangle(10, 20);
    Rectangle r2 = new Rectangle(10, 20);
    if (r1 == r2)
      System.out.println("Same object");
    else
      System.out.println("Different objects");
    if (r1.equals(r2))
      System.out.println("Equal");
    else
      System.out.println("Not equal"); 
  }
}

Since all the important work is done in Rectangle, all we need to do here is instantiate two and compare them using both == and the equals method to see the differing results.

The output should be:

Different objects 
Equal

Testing Strings for Equivalence

Example - write a brief program which has a word stored as a string of text. Print a message asking for a word, read it, then test if they are the same or not.

  • you can preset the message assuming that they are incorrect, then change it if they are correct

Code Sample:

Java-Control/Demos/StringEquals.java
import util.KeyboardReader;

public class StringEquals {
  public static void main(String[] args) {
    String s = "Hello";
    String t = KeyboardReader.getPromptedString("Enter a string: ");
    String message = "That is incorrect";
    if (s.equals(t)) message = "That is correct";
    System.out.println(message);
  }
}
  • try this, then change equals(t) to equalsIgnoreCase(t)

Conditional Expression

Java uses the same conditional expression as C and C++

condition ? expressionIfTrue : expressionIfFalse

This performs a conditional test in an expression, resulting in the first value if the condition is true,or the second if the condition is false

Note: due to operator precedence issues, it is often best to enclose the entire expression in parentheses

Example

Code Sample:

Java-Control/Demos/Conditional.java
public class Conditional {
  public static void main(String[] args) {
    int i = -6;
    String s;
    s = "i is " + ((i >= 0) ? "positive" : "negative");
    System.out.println(s);
  }
}

Note that the parentheses around the test are not necessary by the operator precedence rules, but may help make the code clearer

The parentheses around the entire conditional expression are necessary; without them, precedence rules would concatenate the boolean result onto the initial string, and then the ? operator would be flagged as an error, since the value to its left would not be a boolean.

while and do . . . while Loops

Again, the loops in Java are pretty much the same as in C - with the exception that the conditional expressions must evaluate to only boolean values

while loops

while (condition) statement
;

or

while (condition){
	block of statements
}
  • While Loop Flowchart the loop continues as long as the expression evaluates to true
  • the condition is evaluated before the start of each pass
  • it is possible that the body of the loop never executes at all (if the condition is false the first time)

do ... while loops

do statemen; while (condition);

or

do { block of statements } while (condition;
  • Do ... While Loop Flowchart the condition is evaluated after the end of each pass
  • the body of the loop will always execute at least once, even if the condition is false the first time

for Loops

A for loop uses a counter to progress through a series of values

  • a value is initialized, tested each time through, and then modified (usually incremented) at the end of each pass, before it is tested again
  • the for loop does not do anything that cannot be done with a while loop, but it puts everything that controls the looping at the top of the block

For Loop Flowchart

for (initialize; condition; change)
	statement

or

for (initialize; condition<; change) {
	block of statements
}

for loops can use a variable declared out side of the control portion of the loop or in the control portion. The latter gives the variable block-level scope (existing only for the duration of the loop)

int j;
for (j = 0; j < 12; j++ )
	System.out.print("j = " + j);
for (int j = 0; j < 12; j++ )
	System.out.print("j = " + j);

A for loop may use multiple control variables by using the sequence operator, the comma ( , )

for (int j = 0, k = 12; j <= 12; j++, k-- )
	System.out.println(j + " " + k);

Note: if you use a block-scope variable (such as above) for the first counter, the additional ones will be block scope as well, and also will be the same type of data - i.e., the variable k above also exists only for the duration of the block. There is no way to declare two counters of different types at block-level scope.

Note that neither while nor do . . . while loops allow declaring a looping variable with this type of scope. The looping variable must be declared in advance.

ForEach Loops

Java 5 introduced a new type of loop, the for-each loop. When you have an array or collection class instance, you can loop through it using a simplified syntax

for (type variable : arrayOrCollection) {
	body of loop
}

The looping variable is not a counter - it will contain each element of the array or collection in turn (the actual value and not an index to it, so its type should be the same as the type of items in the array or collection). You can read the : character as if it is the word "from". We will cover this type of loop in more depth in the Arrays section.

For some reason, the looping variable must be declared within the parentheses controlling the loop - you cannot use a preexisting variable.

Since the looping variable is a local variable, it gets a copy of each value from the array or collection. Therefore you cannot use the ForEach loop to write values back to the array/collection - assigning a new value to the variable in the body of the loop is only overwriting the local copy.

Code Sample:

Java-Control/Demos/Loops1.java
public class Loops1 {
  public static void main(String[] args) {
    int i = 1;
    
    System.out.println("While loop:");
    while (i <= 512) {
      System.out.println("i is " + i);
      i *= 2;
    }
    System.out.println("i is now " + i);
    
    System.out.println("Do while loop:");
    do {
      i = i - 300;
      System.out.println("i is now " + i);
    }
    while (i > 0);
    
    System.out.println("For loop:");
    for (i = 0; i < 12; i++) 
      System.out.print(" " + i);
    System.out.println();
    
    System.out.println("For loop that declares a counter:");
    for (int j = 0; j < 12; j++) 
      System.out.print(" " + j);
    System.out.println();
    
    System.out.println("ForEach loop:");
    String[] names = { "Jane", "John", "Bill" };
    for (String oneName : names) 
      System.out.println(oneName.toUpperCase());
  }
}

Additional Loop Control: break and continue

Breaking Out of a Loop

The break statement will end a loop early and execution jumps to the first statement following the loop. The following example prints random digits until a random value is less than 0.1.

Code Sample:

Java-Control/Demos/Break.java
public class Break {
  public static void main(String[] args) {
    for ( ; ; ) { // creates an infinite loop
                  // no initialization, no test, no increment
      double x = Math.random();
      if (x < 0.1) break;
      System.out.print((int) (x * 10) + " ");
    }
    System.out.println("Done!");
  }
}

This code loops, generating and printing a random number for each iteration. If the number is less than 0.1, we break out before printing it.

Code Sample:

Java-Control/Demos/BreakNot.java
public class BreakNot {
  public static void main(String[] args) {
    double x = 0.0;
    for ( x = Math.random(); x >= 0.1; x = Math.random() ) {
      System.out.print((int) (x * 10) + " ");
    }
    System.out.println("Done!");
  }
}

This code avoids the break, by creating and testing the random number in the control part of the loop. As part of the iteration condition, if the number is less than 0.1, the loop simply ends "normally".

Continuing a Loop

If you need to stop the current iteration of the loop, but continue the looping process, you can use the continue statement. Note that:

  • It is normally based on a condition of some sort.
  • Execution skips to the end of this pass, but continues looping.
  • It is usually a better practice to reverse the logic of the condition, and place the remainder of the loop under control of the if statement.

Example - a program to enter 10 non-negative numbers

Code Sample:

Java-Control/Demos/Continuer.java
import util.*;

public class Continuer {
  public static void main(String[] args) {
    int count = 0;
    do {
      int num = KeyboardReader.getPromptedInt("Enter an integer: ");
      if (num < 0) continue;
      count++;
      System.out.println("Number " + count + " is " + num);
    } while (count < 10);
    System.out.println("Thank you");
  }

/*
  // a better way

  public static void main(String[] args) {
    int count = 0;
    do {
      int num = KeyboardReader.getPromptedInt("Enter an integer: ");
      if (num >= 0) {
        count++;
        System.out.println("Number " + count + " is " + num);
      }
    } while (count < 10);
    System.out.println("Thank you");
  }
*/

}

A better way to handle the loop is shown in the commented out version of main - try removing the comment and comment out the original method.

But,continue is easier to use in nested loops, because you can label the level that will be continued

break and continue in Nested Loops

In normal usage, break and continue only affect the current loop; a break in a nested loop would break out of the inner loop, not the outer one

But, you can label a loop, and break or continue at that level. A label is a unique identifier followed by a colon character.

Try the following example as is, then reverse the commenting on the break lines

Code Sample:

Java-Control/Demos/BreakOuter.java
public class BreakOuter {
  public static void main(String[] args) {

    outer: for (int r = 0; r < 10; r++) {
      for (int c = 0; c < 20; c++) {
        double x = Math.random();

        //if (x < 0.02) break;
        if (x < 0.02) break outer;
        System.out.print((int) (x * 10) + " ");
      }
      System.out.println();
    }
    System.out.println("Done!");
  }
}

Game04: Guessing Game with a Loop

Duration: 10 to 20 minutes.

(Optional)

  1. Revise the number guessing program to force the user to guess until they are correct (loop-wise, to keep guessing as long as they are incorrect).
  2. Then add more logic to ask if they want to play again, read a Y or N as their answer, and loop as long as they enter a Y.

Solution:

Solutions/Game04/Game.java
import util.*;
import java.util.*;

public class Game {

---- C O D E   O M I T T E D ----
	public void play() {
		int guess;
		do {
			guess = KeyboardReader.getPromptedInt("Enter a number 1 -100: ");
			if (guess < answer) System.out.println("Too low");
			else if (guess > answer) System.out.println("Too high");
		} while (guess != answer);
		System.out.println("Correct!");
	}
	
	public static void main(String[] args) {
		char playAgain = 'Y';
		do {
			char level = KeyboardReader.getPromptedChar("What level (B, I, A)? ");
			new Game(level).play();		
			playAgain = KeyboardReader.getPromptedChar("Play again (y/n)?: ");
		} while (playAgain == 'Y' || playAgain == 'y');				
	}
}

Payroll-Control02: Payroll With a Loop

Duration: 20 to 40 minutes.
  1. Revise your payroll program to use only one Employee variable.
  2. Use a loop to repeatedly create a new instance to store in it, populate it from the keyboard, and display it on the screen (you can ask after each one if the user wants to enter another, or just use a loop that has a fixed number of iterations).
  3. Also, change the logic for reading the pay rate and department values from the keyboard to use do . . . while loops that will continue to loop as long as the user enters invalid values (note that you will need to separate declaring the variables from populating them - declare each of them before their loop starts, in order for the variable to be available to the test at the end of the loop).

Solution:

Solutions/Payroll-Control02/Payroll.java
import employees.*;
import util.*;

public class Payroll {	
	public static void main(String[] args) {
		Employee e = null;
		String fName, lName;
		int dept;
		double salary;

		for (int i = 0; i < 3; i++) {
			fName = KeyboardReader.getPromptedString("Enter first name: ");
			lName = KeyboardReader.getPromptedString("Enter last name: ");
			>do {
				dept = KeyboardReader.getPromptedInt("Enter department: ");
				if (dept <= 0) System.out.println("Department must be >= 0");
			} while (dept <= 0);
			do {
				salary = KeyboardReader.getPromptedDouble("Enter pay rate: ");
				if (salary < 0.0) System.out.println("Pay rate must be >= 0");
			} while (salary < 0.0);
			e = new Employee(fName, lName, dept, salary);
			System.out.println(e.getPayInfo());
		}
	}
}

Classpath, Code Libraries, and Jar Files

By now, you may have noticed that every demo and solution folder contains its own copy of util and KeyboardReader. Not only is it inefficient, but it means that we would have to locate and update each copy if we wanted to change KeyboardReader.

A better solution would be to have one master copy somewhere that all of our exercises could access.

Using CLASSPATH

Java has a CLASSPATH concept that enables you to specify multiple locations for .class files, at both compile-time and runtime.

By default, Java uses rt.jar in the current Java installation's jre/lib directory, and assumes that the classpath is the current directory (the working directory in an IDE).

If you create a classpath, the default one disappears, so any classpath that you create must include the current directory using a period (.) character.

To use an external library, you would need to create a classpath in one of two ways:

  1. you can create a system or user environment variable, with multiple directories and/or .jar files, separated by the same delimiter used by your PATH (a semicolon in Windows).
  2. You could use the -classpath option in java and javac (-cp is a shorthand version of that).

Here is an example of a pair of commands to compile and run using an external library stored in a jar file. Note that we need the jar file at both compile-time and runtime. The -cp option in both commands replaces the system classpath with one specifically for that command.

javac -cp c:\Java102\ClassFiles\utilities.jar;. *.java 
java -cp c:\Java102\ClassFiles\utilities.jar;. Payroll

Many Integrated Development Environments (IDEs) have a means to specify project properties; usually an item like "Build Path" will have options to specify external jar files (as well as choose from various libraries supplied with the environment).

Creating a jar File (a Library)

If you wish to create your own library of useful classes, you can bundle one or more classes and/or directories of classes in a jar file. Yyou can also add other resources such as image files.

A jar file contains files in a zipped directory structure. The root level of the file structure should match the root of the package structure; for example, to put KeyboardReader in a jar file, we would want to start at the directory where util is visible, and jar that

The following command will create a jar file called utilities.jar for all files in the util package (just KeyboardReader, in this case).

jar -cvf utilities.jar util\*.class
  • The options are create, verbose, and use a list of files supplied at the end of the command.

Creating and Using an External Library

Duration: 5 to 10 minutes.
  1. Run the command shown above to create utilities.jar.
  2. Move utilities.jar to the ClassFiles directory in your filesystem.
  3. If you remove the util directory from Payroll-Control02, you will not be able to compile or run Payroll as we have been doing.
  4. But, you should be able to compile and run by specifiying a classpath option as shown in the commands above.
  5. You can also temporarily modify the classpath for the duration of your command prompt window as follows (note that you may need to modify the line below to match your setup):
    set CLASSPATH=%CLASSPATH%;c:\Java102\ClassFiles\utilities.jar

Compiling to a Different Directory

You can supply a destination directory to the Java compiler, in order to keep your source and compiled files separate:

javac -d destination FileList

The destination directory can be a relative path from the current directory, or an absolute path. It must already exist, but any subdirectories necessary for package structures will be created.

Also, if you have existing compiled files in the source directories (like employees/Employee.class), then the compiler won't recompile those classes (and therefore they won't end up in the destination path) unless they are older than the associated .java file. You would need to "clean" the directories by deleting the .class files first.