Chapter 6.J

Java Conditional Statements

Conditional Statements in Java

Subsections of Java Conditional Statements

If Statement

Now that we’ve covered many different types of conditional constructs, let’s dive right in and see how they can be used in Java.

First, let’s look at the if statement. In Java, the syntax for an if statement is shown below:

if (<Boolean expression>) {
    <true block>
}

As expected, Java will first evaluate the <Boolean expression> to a single Boolean value. If that value is true, it will execute the instructions in the <true block>, which can be one or more lines of code, or even additional constructs as we’ll see later. If that value is false, then the program will simply skip over the <true block> and continue executing the code immediately below the if statement.

It is very important to remember that the Boolean expression is enclosed by parentheses ( ), while the block of code that should be executed if that expression is true is enclosed by curly braces { }, just like a class body or method body.

Let’s take a look at a few code examples, just to see how this construct works in practice. First, let’s consider the program represented by this flowchart from earlier in the chapter:

If-Then Flowchart If-Then Flowchart

This flowchart corresponds to the following code in Java. In this case, we’ll assume x is hard-coded for now:

int x = 1;
if (x > 0) {
    System.out.println(x);
}
System.out.println("Goodbye");

As we can see, this program uses x > 0 as the Boolean expression inside of the if statement. If it is true, then it will output the value of x. So, it is very simple to write code that matches the execution paths shown in a flowchart representing the program.

Here’s one more example. Let’s assume we’d like to write a program that will calculate both the sum and product of two variables, x and y, but only if they are both greater than 0. That program may look like this:

int x = 5;
int y = 7;
if ((x > 0) && (y > 0)) {
    int sum = x + y;
    int product = x * y;
    System.out.println("Sum: " + sum);
    System.out.println("Product: " + product);
}

As we can see, we can create more complex Boolean statements using the various Boolean operators we’ve already learned. In addition, we can include multiple lines of code inside the curly braces.

If-Else Statement

The if-else statement in Java is very similar to the if statement. In Java, the syntax for an if-else statement is shown below:

if (<Boolean expression>) {
  <true block>
} else {
  <false block>
}

As expected, Java will first evaluate the <Boolean expression> to a single Boolean value. If that value is true, it will execute the instructions in the <true block>, which can be one or more lines of code, or even additional constructs as we’ll see later. If that value is false, then the program will execute the code in the <false block> instead. In essence, the if-else statement simply adds a second code block and the else keyword after an if- statement.

Let’s take a look at a few code examples, just to see how this construct works in practice. First, let’s consider the program represented by this flowchart from earlier in the chapter:

If-Then Flowchart If-Then Flowchart

This flowchart corresponds to the following code in Java. In this case, we’ll assume x is hard-coded for now:

int x = 1;
if (x >= 0) {
  System.out.println(x);
} else {
  System.out.println(-1 * x);
}
System.out.println("Goodbye");

As we can see, this program uses x >= 0 as the Boolean logic expression inside of the if-else statement. If it is true, then it will output the value of x. If it is false, the program will output the value (-1 * x), which represents the inverse of x.

Here’s one more example. In this program, we’d like to calculate the difference between two numbers, but we’d only like to output a positive number. So, our code may look something like this:

int x = 3;
int y = 8;
if (x > y) {
  int difference = x - y;
  System.out.println(difference);
} else {
  int difference = y - x;
  System.out.println(difference);
}

In this program, we are simply checking to see if x > y. If so, we know that x - y will be a positive number. Otherwise, we can assume that y - x will be either a positive number or $ 0 $. In either case, we see that this program will produce the correct output.

Missing Curly Braces?

In Java, it is possible to have an If or If-Else statement without curly braces. In that case, the next line of code immediately following the if or else will be the only line considered inside of that branch.

Consider this code for example:

int x = 4;
if (x == 5)
System.out.println(true);
else
System.out.println(false);

This code is valid, and will compile and run properly. However, it is very difficult to read because of the indentation (or lack thereof).

In addition, if we want to add another line of code to each branch, we might accidentally do the following:

int x = 4;
if (x == 5)
x = 0;
System.out.println(true);
else
x++;
System.out.println(false);

This code will not compile and run properly, because the else statement cannot be attached to the appropriate if statement. So, we’d need to add curly braces to make this code make sense to the compiler.

Beyond that, it can be very difficult to read code that is not properly indented, regardless of the use of curly braces. So, it is a best practice to always include curly braces in your conditional statements and indent each block, even if those changes aren’t necessarily required in some cases.

Variable Scope

Now that we’ve learned how to create new code blocks in our programs using constructs such as the if and if-else statements, we must take a minute to discuss one of the major limitations of those code blocks.

The scope of a variable refers to the possible areas in a program’s code where that variable can be accessed and used. This is very important to understand once we begin introducing additional code blocks in our programs, because variables declared inside of a code block cannot be accessed outside of that block.

Local Variable Scope in Java

In general, Java follows these rules when determining if a local variable is accessible.

  1. A local variable in Java may not be accessed before it is first declared.
  2. A local variable in Java may not be accessed outside of the code block in which it is declared.
  3. A local variable in Java may be accessed inside of any code blocks placed inside of the code block in which is declared.
  4. A local variable in Java may not have the same name as an existing variable contained in an enclosing code block.

Later on in this course we’ll learn about class member variables, which have some different rules that govern their scope. For now, we’ll only worry about local variables, which are variables declared within a method body.

Accessing Before Declaring

Let’s look at a few examples for each rule. First, a variable may not be accessed before it is declared. So, we cannot do the following:

public static void main(String[] args){
  x = 5;
  int x;
}

If we do, the compiler will output an error similar to the following:

error: cannot find symbol
    x = 5;
    ^
  symbol:   variable x

Instead, we must make sure the variable is declared before it is accessed, as in this correct example:

public static void main(String[] args){
  int x;
  x = 5;
}

Accessing Outside Code Block

Next, here’s an example where the code is trying to access a variable outside of the code block where it is initially declared:

public static void main(String[] args){
  int x = 5;
  if (x < 10) {
    int y = x + 5;
  }
  System.out.println(y);
}

Once again, the compiler cannot find the variable, and we’ll get an error similar to the following:

error: cannot find symbol
  System.out.println(y);
                     ^
  symbol:   variable y

If we think about this error, it actually makes sense. What if the value of x is greater than $ 10 $? In that case, the program will never execute the line declaring the variable y, so it won’t even exist. Therefore, if we’d like to solve this problem, we can try to declare our variable outside of the if statement’s code block, as in this second example:

public static void main(String[] args){
  int x = 5;
  int y;
  if (x < 10) {
    y = x + 5;
  }
  System.out.println(y);
}

However, this example will also cause the compiler to give us an error:

error: variable y might not have been initialized
  System.out.println(y);
                     ^

In this case, we’ve declared the variable y, but we have not initialized it to a value. Once again, if the value of x is greater than $ 10 $, and the code block inside the if statement is not executed, we won’t know what value should be stored in y. So, the compiler will detect that error and warn us that y may not have been initialized. To solve this error, we simply must assign a value to y before attempting to use it:

public static void main(String[] args){
  int x = 5;
  int y = 0;
  if (x < 10) {
    y = x + 5;
  }
  System.out.println(y);
}

That example will compile and run as expected.

Surprisingly, the Java compiler is advanced enough to determine if a variable would be initialized by all possible paths, as in this example:

public static void main(String[] args){
  int x = 5;
  int y;
  if (x < 10) {
    y = x + 5;
  }else{
    y = x - 5;
  }
  System.out.println(y);
}

This example will compile and run without any problems, since the variable y is initialized in both blocks of the if-else statement. In short, there is no possible execution path that does not initialize y, so it is accepted by the compiler.

Accessing Inside a Code Block

We’ve already seen several examples of this already, but here’s a clear example of accessing a variable from a code block within the block where the variable was declared:

public static void main(String[] args){
  int x = -1;
  if (x < 0) {
    x = -1 * x; 
  }
  System.out.println(x);
}

In this example, the variable x is declared inside of the main method’s body. Then, it is accessed inside of the body of the if statement, which is itself within the body of the main method. Later in this chapter we’ll see examples of chaining and nesting conditional constructs, which will give us more examples of how this works.

Same Variable Names

Finally, Java does not allow us to use the same variable name twice in the same code block, or in any code blocks enclosed within that block. For example, we could try to do something like this:

public static void main(String[] args){
  int x = 5;
  if (x < 10) {
    int x = 15;
    System.out.println(x);
  }
}

If so, the compiler will detect that we’ve already declared variable x in the main method body, so it won’t allow us to declare it again inside the if statement’s body. Instead, it will give us the following error message:

error: variable x is already defined in method main(String[])
    int x = 15;
        ^

We can resolve this error by simply renaming one of the variables:

public static void main(String[] args){
  int x = 5;
  if (x < 10) {
    int y = 15;
    System.out.println(y);
  }
}

We may also choose to use the same variable without declaring it again:

public static void main(String[] args){
  int x = 5;
  if (x < 10) {
    x = 15;
    System.out.println(x);
  }
}

However, we’ll need to remember that it will change the value of the variable outside the block as well.

Lastly, while we cannot use the same variable name as an existing variable, we can use that name later on in our program, as long as there is not already a variable declared with the same name that is accessible. Here’s an example of that:

public static void main(String[] args){
  int x = 5;
  if (x < 10) {
    int y = 15;
    System.out.println(y);
  }
  int y = 12;
  System.out.println(y);
}

Surprisingly, this program will compile and run without any errors. In this case, we are allowed to declare a variable named y inside of the if statement’s code block because we have not yet declared a variable with that name anywhere in our program. Later, we are allowed to declare another variable named y, this time directly within the main method’s body. This is because we are outside of the if statement’s code block, so the previously declared variable named y no longer exists. So, we can reuse the name here without causing any problems.

However, this is widely regarded as poor coding practice, as it may make our code very difficult to read and understand. So, it is always better to try and avoid reusing variable names whenever possible, just to make it clear in our code which to variable we are referring.

Chaining & Nesting

YouTube Video

Video Materials

One of the most powerful features of the conditional constructs we’ve covered so far in this course is the ability to chain them together or nest them within each other to achieve remarkably useful program structures. The ability to use conditional constructs effectively is one of the most powerful skills to develop as a programmer.

Zero, One, Negative One

A great example of the many ways to structure a program using conditional constructs is building a simple program that does three things:

  1. If x is less than $ 0 $, output -1
  2. If x is equal to $ 0 $, output 0
  3. If x is greater than $ 0 $, output 1

Let’s look at two different ways we could structure this program using the conditional constructs we’ve already learned.

Try It!

See if you can build each of these examples in a file named Conditionals.java, either in Codio or on your own computer. Doing so is a great way to get additional practice working with conditional constructs.

Don’t forget to add the correct class declaration to the file as well! It is not included in the code examples below.

Chaining If Statements

First, we could chain together several if statements to create this program. As a flowchart, the program might look like this:

Chaining If Statements Chaining If Statements

Here’s one way that program could be written in Java. Once again, we’re just using hard-coded variables for now:

public static void main(String[] args) {
  int x = 3;
  if (x < 0) {
    System.out.println(-1);
  }
  if (x == 0) {
    System.out.println(0);
  }
  if (x > 0) {
    System.out.println(1);
  }
}

Just like we see in the flowchart, this program contains three if statements chained together, one after another. If we run this program with various inputs, we should see that it produces the expected result.

Nesting If-Else Statements

Next, we can achieve the same result using if-else statements. Here’s what that program would look like as as flowchart:

Nesting If-Else Statements Nesting If-Else Statements

We can also write that program in Java. Here’s one possible way to do it:

public static void main(String[] args) {
  int x = 3;
  if (x < 0) {
    System.out.println(-1);
  } else {
    if (x == 0) {
      System.out.println(0);
    } else {
      System.out.println(1);
    }
  }
}

In this example, we’ve nested an if-else statement inside of the second block of another if-else statement. So, if the first Boolean expression, x < 0, is true, we’ll output -1 and end the program. However, if it is false, we’ll go into the false branch of the first statement. Then, we’ll see another Boolean expression, x == 0. If that expression is true, we’ll output 0. Otherwise, we’ll output 1. Once again, this program should produce the correct result.

Of course, we could include a third if-else statement, as shown in this example:

public static void main(String[] args) {
  int x = 3;
  if (x < 0) {
    System.out.println(-1);
  } else {
    if(x == 0){
      System.out.println(0);
    }else{
      if(x > 0){
        System.out.println(1);
      }else{
        //this should never happen
        System.out.println("ERROR!");
      }
    }
  }
}

As we discussed earlier in this chapter, we can safely infer that x must be greater than $ 0 $ based on the two previous Boolean expressions. However, what if we’ve made a logic error in our program, and that assumption is not valid? By including the last if-else statement, we can have our program print an error in the unlikely chance that the value of x is not properly handled by any other option.

In fact, here’s how easy it is to cause just that sort of logic error. Consider the following example:

public static void main(String[] args) {
  int x = 3;
  if (x < -1) {
    System.out.println(-1);
  } else {
    if (x == 0) {
      System.out.println(0);
    } else {
      if (x > 1) {
        System.out.println(1);
      } else {
        //this should never happen
        System.out.println("ERROR!");
      }
    }
  }
}
Spot The Error

Can you spot the logic error in the example above? Try running the code with different values for x and see what happens.

In this example, we’ve updated the Boolean expression in the first if-else statement to x < -1. Similarly, we’ve changed the Boolean expression in the third statement to x > 1. That change seems logical, right?

What if the value of x is exactly $ 1 $ or $ -1 $? In that case, it will fail all three logic expressions, and the program will output ERROR! instead. By including the third if-else statement, we can detect logic errors that may not easily be found otherwise. Without that statement, the program would most likely output 1 even when the input is -1, which is clearly a negative number.

So, in many cases, it may be better to include additional if-else statements to explicitly check all possible cases, instead of relying on assumptions and inferences, which may lead to unintended logic errors.

Inline Nesting

Finally, it is acceptable to minimize nested if-else statements if the nested statement is exclusively in the false branch. Here’s an example:

public static void main(String[] args) {
  int x = 3;
  if (x < 0) {
    System.out.println(-1);
  } else if (x == 0) {
    System.out.pr intln(0);
  } else if (x > 0) {
    System.out.println(1);
  } else {
    //this should never happen
    System.out.println("ERROR!");
  }
}

Many programming languages refer to this structure as an if-else if-else statement. In this program, if the first Boolean expression is false, it immediately moves to the next Boolean expression following the first set of else if keywords. The process continues until one Boolean expression evaluates to true, or the final else keyword is reached. In that case, we know that none of the Boolean expressions evaluated to true, so the final false branch is executed.

While other programming languages include an explicit keyword for else if, Java does not. Instead, we are simply omitting the curly braces that surround the false block on each if-else statement. The Java compiler treats the entire if-else statement contained in that block as a single line, so the curly braces are not explicitly required in this case.

This is the one and only case where it is acceptable to remove those unnecessary curly braces in most Java style guides. Some programmers find this inline structure simpler to read and follow, as it avoids the problem of code being nested several layers deep. Others feel that it is dangerous to remove any explicit curly braces, and prefer the nested structure instead.

Communicating Mutual Exclusion

It should be obvious that only one branch of an if-else if-else will ever be executed, because the branches are mutually exclusive. It is a excellent practice to always use the if-else if-else structure whenever you know the logic should be exclusive. Consider a simple program we call the “Goldilocks Porridge Temperature Tester”.

if (isTooHot) { 
   System.out.println("porridge is too hot");
}
if (isTooCold) {
   System.out.println("porridge is not hot enough");
}
if (isJustRight) {
    System.out.println("porridge is perfect");
}

A future reader of the program, unfamiliar with the children’s tale, would see there are 3 boolean variables (isTooHot, isTooCold, isJustRight) and, depending on their values,up to 3-lines might get printed. This is because we cannot safely assume that just one of them will be true. In fact, they could all three be true!

However, if we rewrite the program to use if-else if-else instead of just if-else statements:

if (isTooHot){ 
   System.out.println("porridge is too hot");
}else if (isTooCold) {
   System.out.println("porridge is not hot enough");
}else if (isJustRight) {
    System.out.println("porridge is perfect");
}

the future reader would know that only one line should ever be printed.

Subsections of Chaining & Nesting

Switch Statement

Next, let’s look at the switch statement in Java. As we learned earlier, this statement allows our programs to choose branches based on any number of possible values of a variable. Here’s a flowchart showing what such a program might look like:

Switch Statement Switch Statement

In Java, we could write that program in many ways. This is one possible solution:

public static void main(String[] args) {
  int x = 2;
  switch (x) {
    case 1: System.out.println("A");
      break;
    case 2: System.out.println("B");
      break;
    case 3: System.out.println("C");
      break;
    default: System.out.println("Error!");
      break;
  }
}

In this program, the switch statement will evaluate the value of x, then look for the case keyword that exactly matches that value. In this example, x == 2, so it will choose the second case and output B.

If we change the value of x to $ 4 $, then we can see that none of the case keywords match. In that instance, the program will instead choose the default case and print Error!.

Fall Through

The switch statement above introduces a new keyword, break, which we’ll cover in detail in a later chapter. The break keyword causes the program to stop executing code in the current statement, and the continue executing the code following that statement. So, when the program reaches a break statement in the example above, it stops executing any additional code in the switch statement and continues running the code following that statement.

It is possible to create a switch statement that does not include break keywords. In that statement, it will continue executing any cases below the chosen case until it reaches the end of the statement or the default keyword.

For example, let’s say we’d like to write a program that will print all the numbers from a given starting number up to 5. So, we could use a switch statement to do that as in this program:

public static void main(String[] args){
  int x = 2;
  switch (x) {
    case 1: System.out.println("1");
    case 2: System.out.println("2");
    case 3: System.out.println("3");
    case 4: System.out.println("4");
    case 5: System.out.println("5");
      break;
    default: System.out.println("Error!");
      break;
  }
}

When we compile and run this program, we’ll receive the following output:

2
3
4
5

The program will start at case 2:, since x == 2, and print 2. Since there is not a break keyword in that case, it will continue to the next case, printing 3, then 4, then 5 before it finally reaches a break keyword.

Further Reading

Switch Statements are not used as often as other conditional constructs, but they can be a useful to a program in certain instances. There are many other unique ways they could be used. To learn more, refer to the official Java documentation on The switch Statement .

Ternary Statement

Java also includes the ternary conditional operator, which can be used as a shortcut for an if-else statement.

First, consider the flowchart we saw earlier in this chapter:

Ternary Operator Flowchart Ternary Operator Flowchart

In Java, this flowchart could be represented by the following code:

public static void main(String[] args){
  int x = 3;
  int y = 5;
  int z = (x > y) ? x : y;
  System.out.println(z);
}

In this program, the expression (x > y) ? x : y; is the ternary conditional operator. It first calculates the value of the Boolean expression (x > y). If that expression is true, then the entire expression evaluates to x. If it is false, then the expression evaluates to y. So, if we compile and run this program, it should output 5.

We can also include the ternary conditional operator anywhere we’d normally use a value. This is because, just like any other operator, the ternary conditional operator results in a single value when evaluated. For example, it could be used directly within the println() method:

public static void main(String[] args){
  int x = 3;
  int y = 5;
  System.out.println((x > y) ? x : y);
}

Subgoals

Now that we’ve seen how to work with conditional constructs in Java, let’s break down our thought process a bit into subgoals once again.

Evaluating Conditional Constructs

Here are the subgoals we can use for evaluating conditional constructs:

1. Diagram Which Statements Go Together

First, when we see a conditional construct in code, we must determine which statements go together. Specifically, we need to know which statements are in the true branch, and which statements are in the false branch. This may seem pretty straightforward, but if the code contains many nested statements or poor formatting, it can be very difficult to do.

Here’s a quick example:

if(true){
  if(false){
    System.out.println("one");
  }
}else{
  System.out.println("two");
}

In this example, we see that there are three distinct branches. First, we have the true branch of the outermost if-else statement, which includes the inner if statement. That statement itself has a true branch that will print one to the terminal. The outermost statement also has a false branch, which will print two to the terminal.

2. Determine if Boolean Expression is true or false

Once we’ve identified our conditional construct, the next step is to determine if the Boolean expression inside of the if statement is true or false. Sometimes this step is simple, but other times it can be tricky. Thankfully, we can refer back to the subgoals we’ve already seen for evaluating expressions if we need a bit of help.

3. Follow the Correct Branch

After we find the value of the Boolean expression, we can simply follow the true branch if the Boolean expression evaluated to true. If the Boolean expression evaluated to false, we can follow the false branch if it exists. If the conditional construct is an if statement, we can simply ignore the rest of the conditional construct and move on to the next part of the program.

Writing Conditional Constructs

We can also use subgoals to help us write new conditional constructs.

1. Define How Many Mutually Exclusive Paths are Needed

First, when building a new conditional construct, we must determine how many paths are needed. In effect, this will be one more than the number of conditional constructs we’ll end up using in most cases.

2. Order Paths from Most Restrictive/Selective to Least Restrictive

Next, we can reorder the paths from most restrictive to least restrictive. The most restrictive path would be the one that is least likely to occur, while the least restrictive path is the one that will be taken most often. We’ll see how this works a bit more clearly in the example on the next page.

3. Write if Statement with Boolean Expression

Once we have all of our paths identified and ordered, we can start writing our if statements for each path. Each if statement will need a Boolean expression to help us determine which branch to follow. Generally, we’ll place the most restrictive paths inside of the less restrictive paths, but it all depends on the problem and what order makes the most sense.

4. Write true Branch Code

Then, for each conditional construct, we must fill in the code on the inside of the true branch. This could be a block of code, or even another conditional construct.

5. Write false Branch Code

Similarly, we must fill in the code on the inside of the false branch, if needed. This could be a block of code, or even another conditional construct.

6. Repeat 3-5 For All Paths

Finally, once we’ve created a conditional construct, we may have to repeat these steps again and again for each path that we identified in subgoal 1.

On the next page, we’ll see how we can apply these subgoals in a worked example.

A Worked Example

YouTube Video

Video Materials

We’ve covered quite a bit of new material so far in this chapter. Let’s work through a complete example from start to finish, just to see how we can put all of those pieces together to make a very powerful program.

Problem Statement

First, let’s start with a problem statement. Here’s an interesting problem that we can try to solve:

Write a program that receives a positive integer as input from the user that represents a year, and prints whether that year is a leap year or not. If the year is a leap year, it should print output similar to 2000 is a Leap Year. If not, it should print output similar to 2001 is not a Leap Year.

While this sounds like a simple problem, there are actually several rules we’ll have to handle. According to the United States Naval Observatory, the Gregorian calendar (the calendar in use throughout much of the world) calculates whether a year is a leap year based on the following rule:

Every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these centurial years are leap years if they are exactly divisible by 400. For example, the years 1700, 1800, and 1900 are not leap years, but the year 2000 is. - Source

So, we’ll be writing a program that can handle all of those rules to determine if a year is a leap year or not.

Getting Started

To begin building this program, we need to first build the basic skeleton of a program. For this example, store your code in a file called Example.java

Codio Tutorial

If you are following along with this course in Codio, this example will be a graded exercise in that tutorial. So, you may wish to make sure Codio is open and follow along with this example from there.

First, we’ll need to include a class declaration and a main method declaration in the file. Our code should be similar to this:

public class Example{
  
  public static void main(String[] args){
    
  }
  
}

Breaking Down the Problem Statement

Before we start writing more code, let’s break down the problem statement a bit and see how we can use it to help us identify parts of the program we need to write.

Here’s our problem statement again:

Write a program that accepts a positive integer from the command line, that represents a year as a command line argument, and prints whether that year is a leap year or not. If the year is a leap year, it should print output similar to 2000 is a Leap Year. If not, it should print output similar to 2001 is not a Leap Year.

Looking at this problem statement, we see that we need at least one variable to store the year that is provided as input from the user. Similarly, we need at least one conditional construct, which will allow us to print whether the given year is a leap year or not. Here’s the problem statement again, with those parts highlighted:

Write a program that accepts a positive integer (variable) from the command line, that represents a year, and prints whether that year is a leap year or not (conditional construct). If the year is a leap year (true branch), it should print output similar to 2000 is a Leap Year. If not (false branch), it should print output similar to 2001 is not a Leap Year.

Similarly, we can look at the second part of the problem statement and break it down as well:

Every year that is exactly divisible by four is a leap year, except for years that are exactly divisible by 100, but these centurial years are leap years if they are exactly divisible by 400. For example, the years 1700, 1800, and 1900 are not leap years, but the year 2000 is. - Source

Looking at this, we see that there are actually three conditional constructs we need to build. Let’s mark them in the statement:

Every year that is exactly divisible by four (conditional construct 1) is a leap year, except for years that are exactly divisible by 100 (conditional construct 2), but these centurial years are leap years if they are exactly divisible by 400 (conditional construct 3). For example, the years 1700, 1800, and 1900 are not leap years, but the year 2000 is. - Source

Of course, we’ll have to be careful to make sure that each branch of these three conditional statements produces the correct output. Below, we’ll see how we can construct code for this problem.

Reading Input

Generally, one of the first things our program should do is read and process the input. So, we’ll add the few lines we need to import the Scanner library, create a Scanner object, and read an input from the user:

import java.util.Scanner;

public class Example{
  
    public static void main(String[] args){
        Scanner scanner = new Scanner(System.in);
        int year = scanner.nextInt();

        // MORE CODE GOES HERE  
    }
}

We’ve used code similar to this in several of our projects at this point, so it should be familiar to us, even if we haven’t written it before. We’ll explain more about the structure of this code later in this course. For now, we can simply copy and paste this example and build upon it.

Once we have that code in place, we are ready to begin working on the actual logic of our program. The code examples below will just include the logic portion of the program, which can be placed where the MORE CODE GOES HERE line is in the structure above.

Verifying Input

Now that we’ve read the input, we can start writing the logic of our program. However, the problem statement has one very important part in it that we’ll need to handle as well:

Write a program that accepts a positive integer that represents a year…

So, we’ll need to make sure the user has input a positive integer into our program. We can do that using a simple if-else statement:

if(year <= 0){
    System.out.println("Error");
}else{
  
}

In this case, we might be tempted to use an if statement instead. However, we need to make sure our program only calculates a result if the input is positive. If it is negative, we should just print an error. Since that means our program needs two distinct branches, we should use an if-else statement here.

Calculating Output

Once we’ve read our input, we need to calculate our output. Let’s handle the three rules one at a time.

Divisible by $ 4 $

For the first rule, we must check to see if the year is divisible by $ 4 $. We can use the modulo operator to do this. Remember that the modulo operator performs a division and returns the remainder. So, if the remainder is $ 0 $, then we know that the number is evenly divisible by the divisor.

In code, we could do the following:

if(year <= 0){
    System.out.println("Error");
}else{
    if(year % 4 == 0){
        //divisible by 4
    }else{
        //not divisible by 4
        System.out.println(year + " is not a Leap Year");
    }
}

In each of these if-else statements, let’s place a quick comment in the code to keep track of what we know within each branch.

Not Divisible by $ 100 $

Next, we need to handle the rule that any year divisible by $ 100 $ is not a leap year, even if it is divisible by $ 4 $. Of course, we can easily determine mathematically that all years divisible by $ 100 $ are also divisible by $ 4 $, so we don’t have to worry about the other case in this instance. So, we can add another if-else to our program:

if(year <= 0){
    System.out.println("Error");
}else{
    if(year % 4 == 0){
        //divisible by 4
        if(year % 100 == 0){
            //divisible by 4 and 100
            System.out.println(year + " is not a Leap Year");
        }else{
            //divisible by 4 but not 100
            System.out.println(year + " is a Leap Year");
        }
    }else{
        //not divisible by 4
        System.out.println(year + " is not a Leap Year");
    }
}

In this case, we chose to next our if-else statement inside of the previous statement. So, if the year is divisible by $ 4 $ and also divisible by $ 100 $, we print Not a Leap Year. If it is divisible by $ 4 $ and not divisible by $ 100 $, which is the false branch of the innermost if-else, we know that it must be a leap year, so we can print Leap Year.

Unless Divisible by $ 400 $

There’s one more rule we must add, which is the rule that a year divisible by $ 400 $ must be a leap year, even though it is also divisible by $ 100 $. So, we must add one additional if-else statement. But where?

If we think back through the rules, we know that this rule is only in effect when the year is both divisible by $ 4 $ and $ 100 $. So, we’ll need to add one more statement in the true branch of the innermost if-else:

if(year <= 0){
    System.out.println("Error");
}else{
    if(year % 4 == 0){
        //divisible by 4
        if(year % 100 == 0){
            //divisible by 4 and 100
            if(year % 400 == 0){
                //divisible by 4 and 100 and 400
                System.out.println(year + " is a Leap Year");
            }else{
                //divisible by 4 and 100 but not 400
                System.out.println(year + " is not a Leap Year");
            }
        }else{
            //divisible by 4 but not 100
            System.out.println(year + " is a Leap Year");
        }
    }else{
        //not divisible by 4
        System.out.println(year + " is not a Leap Year");
    }
}

Now we’ve created a program that should be able to tell us if a year is a leap year or not.

Other Solutions

Of course, there are many other ways this program could have been structured that would produce the same output. For example, instead of using nested if-else statements, we could rearrange them a bit to make them inline if-else if-else statements, as in this example below:

if(year <= 0){
    System.out.println("Error");
}else if(year % 400 == 0){
    //divisible by 400
    System.out.println(year + " is a Leap Year");
}else if(year % 100 == 0){
    //divisible by 100 but not 400
    System.out.println(year + " is not a Leap Year");
}else if(year % 4 == 0){
    //divisible by 4 but not 100
    System.out.println(year + " is a Leap Year");
}else{
    //not divisible by 4
    System.out.println(year + " is not a Leap Year");
}

In fact, with a bit of thinking, we could reduce most of this program to a single Boolean logic expression, as in this example

if(year <= 0){
    System.out.println("Error");
}else if(((year % 4 == 0) && (year % 100 != 0)) || (year % 400 == 0)){
    System.out.println(year + " is a Leap Year");
}else{
    System.out.println(year + " is not a Leap Year");
}

Any of these solutions is just as correct as the one we built above. It really only depends on which solution makes the most sense to us and is the easiest for us to write and debug.

Subsections of A Worked Example