Suggested reading: sections 3.2-3.6

Decision Structures

A decision structure is a construct in a computer program that allows the program to make a decision and change its behavior based on that decision. The decision is made based on the outcome of a logical test. A logical test is a calculation whose outcome is either true or false.

If statements

The if statement is the simplest example of a decision structure in Java. In an if statement a logical test is made which can evaluate to either true or false. If the result of the test is true, the statements in the if branch are executed. If the result of the test is false, the statements in the else branch are executed. Once the statements in the appropriate branch have been executed, the flow of execution continues on to the statements that follow the if statement.

Here is a simple example. Below is the code for program that computes and prints the absolute value of a number the user inputs.

public class AbsValue {

  public static void main(String[] args) {
    int x, absX;

    Scanner input = new Scanner(System.in);
    System.out.print("Enter an integer x: ");
    x = input.nextInt();

    // if statement starts below
    if (x < 0) {
      // This is the if branch
      absX = -x;
    } else {
      // This is the else branch
      absX = x;
    }

    System.out.println("The absolute value of "+x+" is "+absX);
  }
}

The test in the if statement is a simple comparison test that checks whether or not the value stored in x is positive. If the test evaluates to true, the code following the test is executed. If the test evaluates to false, the code following the else is executed instead. Note that curly braces are used to delimit the two branches.

The exact placement of the curly braces is a matter of taste. Each of the following forms is legal:

if(x < 0)
  {
  absX = -x;
  }
else
  {
  absX = x;
  }

if(x < 0) { absX = -x; }
else { absX = x; }

In cases where the body of one of the branches of the if statement reduces to just a single statement, the curly braces are optional.

if(x < 0)
  absX = -x;
else
  absX = x;

The else branch is also optional. In cases where the logic requires only that something be done when a particular condition applies, only the if branch may be necessary. For example, we can write the program above this way.

public class AbsValue2 {

  public static void main(String[] args) {
    int x, absX;

    Scanner input = new Scanner(System.in);
    System.out.print("Enter an integer x: ");
    x = input.nextInt();

    absX = x;
    if (x < 0)
      absX = -x;

    System.out.println("The absolute value of "+x+" is "+absX);
    }
}

Comparison tests

The next few example programs we are going to see are all programs that do numerical calculations. In numerical programs, most decisions are made on the basis of numerical comparisons. The table below shows the comparison operators available for use in Java.

OperatorMeaning
==is equal to
!=is not equal to
<is less than
<=is less than or equal to
>is greater than
>=is greater than or equal to

Important warning: note that the comparison operator for equality is ==, not =. This leads to one of the more common mistakes that beginning Java programmers make. The follow code is legal, but does not do what you might expect.

if(n = 10)
  n = 0;

What happens in this case is that rather than testing whether or not n equals 10, the code in the test actually sets n equal to 10. (After that the code in the if branch gets executed anyway, regardless of the original value of n.)

Nested if statements

An if statement gives us the ability to ask a single question and do something in response to that question. Often, the logic of a particular situation will dictate that we ask more than one question to resolve a situation. In that case, we may need to make use of a nested if. The following example demonstrates how this works in practice. The program below has the user enter three floating point numbers that form the coefficients of a polynomial p(x) = a x2 + b x + c. The program will then try to determine what kind of roots the polynomial has.

The key decision that has to be made is based on the value of the descriminant, b2 - 4 a c. If that quantity is negative, the polynomial has no real roots. If it 0, the equation has one real root, and if it is greater than 0 there are two distinct real roots. The program below uses a pair of nested if statements to determine which of these three cases we are dealing with.

public class PolynomialRoots {

  public static void main(String[] args) {
    double a, b, c, desc;

    Scanner input = new Scanner(System.in);
    System.out.print("Enter the coefficients a, b, and c: ");
    a = input.nextDouble();
    b = input.nextDouble();
    c = input.nextDouble();

    desc = b * b - 4 * a * c;

    if (desc < 0.0) {
      System.out.println("The polynomial has no real roots.");
    } else {
      if (desc == 0.0) {
          System.out.println("The polynomial has one real root.");
      } else {
          System.out.println("The polynomial has two real roots.");
      }
    }
  }
}

The first if test distinguishes between the case where there are no real roots and the case where there are real roots. Within the second case we have to ask a further question to determine if there is a repeated real root or two distinct real roots. We do this by simply placing a second if statement inside the else part of the first if statement.

Chained if-else statements

The example above is an example of a categorization task. The answer we are looking for can fall into one of three categories, so we ask a couple of questions to determine which of the three categories we have. Note that to accomplish the categorization we have to nest the if statement that asks the second question inside the else part of the if statement that asks the first question.

Since an entire if-else construct is considered to be a single compound statement in Java, we can take advantage of the special rule that says that if the if or else part of an if-else statement reduces to just a single term we can eliminate the curly braces. Deploying that rule converts the structure of the code to something like this:

if (desc < 0.0)
   System.out.println("The polynomial has no real roots.");
else
   if (desc == 0.0)
      System.out.println("The polynomial has one real root.");
   else
      System.out.println("The polynomial has two real roots.");

Eliminating the line break between the else and if and modifying the structure slightly produces this form.

if (desc < 0.0)
   System.out.println("The polynomial has no real roots.");
else if (desc == 0.0)
   System.out.println("The polynomial has one real root.");
else
   System.out.println("The polynomial has two real roots.");

The resulting form is identical syntactically and functionally to the nested-if form we started with above. This so-called chained if-else form is very useful for categorization. Using this form we can ask a series of questions designed to determine which of several categories applies in a given situation.

Here is an example from the text. ComputeBMI.java computes a Body Mass Index, which is a single number used to classify individuals as underweight or overweight. BMI is defined as a person's weight in kilograms divided by their height in meters squared. The program below does the necessary arithmetic to compute a BMI value from weight and height data a user inputs. To make that index more meaningful, the program also provides interpretation. There is a BMI scale that can be used to determine whether a person is seriously underweight, underweight, normal weight, overweight, or seriously overweight. The program uses a chained if-else construct to determine which of these five categories the user's BMI places them in.

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

    // Prompt the user to enter weight in pounds
    System.out.print("Enter weight in pounds: ");
    double weight = input.nextDouble();

    // Prompt the user to enter height in inches
    System.out.print("Enter height in inches: ");
    double height = input.nextDouble();

    final double KILOGRAMS_PER_POUND = 0.45359237; // Constant
    final double METERS_PER_INCH = 0.0254; // Constant

    // Compute BMI
    double weightInKilograms = weight * KILOGRAMS_PER_POUND;
    double heightInMeters = height * METERS_PER_INCH;
    double bmi = weightInKilograms /
        (heightInMeters * heightInMeters);

    // Display result
    System.out.println("BMI is " + bmi);
    if (bmi < 18.5)
      System.out.println("Underweight");
    else if (bmi < 25)
      System.out.println("Normal");
    else if (bmi < 30)
      System.out.println("Overweight");
    else
      System.out.println("Obese");
  }
}

Compound tests

Some questions we would like to ask can not be phrased readily as simple comparisons. To make it possible to form more complex tests, Java allows you to form compound tests by combining simple tests with logical operators. The table below shows the three logical operators that can be used to form compound tests.

operatorinterpretation
&&logical and
||logical or
!logical negation

Compound tests are formed in a fairly natural way by combining simple tests. For example, here is a compound test to determine whether or not the integer variable x falls in the range between 5 and 10, inclusive.

if((x >= 5)&&(x <= 10))
  System.out.println("x falls in the range [5,10]");

Here is another example that uses a compound test. Some months of the year have 31 days, while others have 30 days. Here is an example of some logic that can determine how many days are in a given month:

int month, days;
Scanner input = new Scanner(System.in);

System.out.print("Enter a month number from 1 to 12: ");
month = input.nextInt();

if(month == 2) {
  System.out.println("Assuming this year is not a leap year...");
  days = 28;
} else if(month == 1 || month == 3 || month == 5 || month == 7
            || month == 8 || month == 10 || month == 12) {
  days = 31;
} else {
  days = 30;
}
System.out.println("This month will have "+days+" days.");

DeMorgan's law and compound tests

Here is a moderately complex problem in setting up an if test. Suppose you are given two intervals [a,b] and [c,d] on the real line and you have to determine whether or not the intervals intersect. Because the intervals can be anywhere on the real line, there are all kinds of ways that the two intervals can either intersect or not intersect with each other:

Given this rather exhaustive list of possibilities, we can construct a moderately complex chain of if-else statements to capture all possible cases.

For this particular problem, there is a simpler alternative approach: we can try to characterize the ways that these two intervals can not overlap, and construct a simple test for that. There are only two ways that the two intervals can not overlap:

Constructing a compound test to check for these two cases is easy:

if(b < c || d < a) {
  // Intervals do not overlap
} else {
  // Intervals do overlap
}

Suppose now that we wanted to do something special only in the case when the intervals overlap. That would cause us to set up a structure something like this:

if(b < c || d < a) {
} else {
  // Do something with overlapping intervals
}

This structure is legal, but a little clunky. It is actually OK to set up an if branch in an if-else statement that does nothing.

A somewhat better alternative is to use the logical not operator, !, to test for the negation of <the intervals do not overlap>. If we replace the test above with its negation, that would allow us to switch the if and else cases:

if(!(b < c || d < a)) {
  // Do something with overlapping intervals
} else {
}

Now that we have moved the empty branch to the else branch, we can use the "optional else" rule to drop the now useless else part:

if(!(b < c || d < a)) {
  // Do something with overlapping intervals
}

One final complaint we can lodge against this solution is that the test is still a bit clunky, since it effectively expresses a double negation: it is not true that the intervals do not overlap. In cases like this, we can use one of two useful logical transformation rules known as DeMorgan's laws to simplify the situation. Given two logical propositions A and B, DeMorgans laws allow us to simplify certain expressions involving negations and logical combinations of these:

not (A and B) ⇔ (not A) or (not B)

not (A or B) ⇔ (not A) and (not B)

We can use the second of these two forms to simplify the test we have:

!(b < c || d < a)(!(b<c))&&(!(d<a))

We can further simplify this by removing the negations:

(!(b<c))&&(!(d<a))(b >= c)&&(d >= a)

This final test is the correct test to determine whether or not the two intervals overlap.

if((b >= c)&&(d >= a)) {
  // Do something with overlapping intervals
}

Determining directly that this is the appropriate test for overlapping intervals would have been quite difficult.

The moral of the story here is that if you can not come up with a direct test for some condition, try instead constructing the test for not its negation, and then use one of DeMorgan's laws to simplify the test expression.

Precedence rules and logical combinations

Arithmetic operators obey a set of precedence rules. For example, the operation of multiplication has higher precedence than the operation of addition. Consider the expression

a + b*c

The precedence rules for arithmetic specify that we should do the multiplication here first, and then the addition. If we want to do the operations in a different order, we have to use parentheses to override the precedence rules. The expression

(a + b)*c

says explicitly to do the addition first, and then the multiplication.

Precedence rules also play a role in structuring logical expressions for use in if-else statements. The basic precedence rules for logical expression are that

The precedence rules tell us that the parentheses around the comparisons in

(b >= c)&&(d >= a)

are not necessary. If we write

b >= c && d >= a

we will get the same result, since the comparison operators >= have higher precedence than the logical operator && and will get evaluated first, just as we desire.

So now we arrive at the final form of our test for overlapping intervals:

if(b >= c && d >= a) {
  // Do something with overlapping intervals
}