Lesson Weekend

The applications we've written in Java thus far are still fairly simple. Definitely more simple than those you just recently completed in Intro to Programming. Let's increase the complexity by exploring how Java implements yet another classic programming concept: Booleans, and how to use booleans to execute programmatic branching.

As you learned in Intro, branching is simply a manner of executing different code depending on some circumstance in your program. You should have received plenty of JavaScript branching practice in your previous course. But in this lesson we'll walk through how Java implements two different types of branching: Branching using Booleans, and branching using logical operators. Let's get started!

Branching with Booleans

First, let's refresh our knowledge on Booleans. As you know, booleans hold one of two values: true or false.

Let's open the Java REPL and observe several operators that return booleans using the equality operator. Similar to JavaScript, the Java equality operator == asks if something is equal to something else. If so, Java returns a Boolean with a true value. If not, a Boolean with a false value.

In JavaScript, you may have worked with ===, or strict equality, which determined whether two things were not just the same but completely identical. In Java, this determination is made differently, which we'll address soon. In Java, however, we will only use two == to evaluate equality.

Comparison Operators and Booleans

We can compare two items like this:

> 1 == 1;
java.lang.Boolean res0 = true

Because these two numbers are equal, the REPL creates a Boolean variable with a true value. But what if we compare things that aren't the same?:

>  "1" == "2";
java.lang.Boolean res1 = false

In this case the Java REPL creates a Boolean variable set to `false. Easy enough.

Creating Variables with Booleans

We can also declare our own Boolean variables:

> Boolean isMathWrong = 1 > 2;
java.lang.Boolean isMathWrong = false
> Boolean isMathRight = 1 < 2;
java.lang.Boolean isMathRight = true

Here we are creating our Booleans using the relational operators > and < to check if an Integer is greater than or less than another operator. Once the result is processed into a Boolean statement, we save it into variable.

Booleans and Branching

Using Booleans, we can add branching logic in the applications we write. Let's write an if/else statement to see if someone is old enough to watch an R-rated movie in theaters:

src/main/java/RatedR.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class RatedR {

   public static void main(String[] args) {
       System.out.println("How old are you?");
       try{
           BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
           Integer integerUserAge = Integer.parseInt(bufferedReader.readLine()); //we are parsing here
           if ( integerUserAge >= 17 ) {
               System.out.println("You can see the movie!");
           } else {
               System.out.println("I'm sorry, you are too young to see that movie.");
           }
       }

       catch(IOException e)
       {
           e.printStackTrace();
       }
   }
}

Here, we convert the user's input to an integer and use the relational operator (>=) to check if their age is greater than or equal to 17. If the code after if, ( integerUserAge >= 17 ), evaluates to true, Java will run all the code between the first set of curly braces.

If it evaluates to false, Java will run all the code between the set of curly braces that follows else. As you can see, this functions in the same manner as the JavaScript if/else statements we used in Intro to Programming.

Methods Returning Booleans

There are also a lot of methods that will return a Boolean. Let's create a statement using one of these methods.

src/main/java/StartsWithZ.java
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class StartsWithZ {

   public static void main(String[] args) {
       System.out.println("What's your name?");
       try {
           BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
           String userName = bufferedReader.readLine();
           if (userName.startsWith("Z")) {
               System.out.println("Your name starts with a Z!");
           } else {
               System.out.println("Your name doesn't start with a Z :(");
           }
       }

       catch(IOException e)
       {
           e.printStackTrace();
       }
   }
}

The parentheses after the if statement is where we store our statement that returns a boolean. We used userName.startsWith("Z") here. The method startsWith("Z") is called on the userName variable. It will take the string argument passed to it, "Z" and compare it to the first letter of userName. It will return true if it is equal to the argument, and false if not. The Z here is case-sensitive.

Branching with Conditionals

But sometimes, if and else aren't quite enough to handle more complex situations. Before we move on, let's also explore several logical operators to handle more more complex branching. We'll walk through the process of creating a small application that informs the user how expensive their hotel stay is likely to be, based on several varying conditions.

Application Setup

Let's say we want to figure out if someone's hotel stay is likely going to be expensive based on the current season, and whether they're booking a stay on the weekend, or a weekday. After all, hotels are usually more expensive during peak travel season (summer) and on the weekends, when more people are likely to take trips out of town.

We'll begin by collecting information about what season and time of week the user will be traveling in:

src/main/java/Hotel.java
public class Hotel {

   public static void main(String[] args) {


       try {
           BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
           System.out.println("In what season are you booking a stay?");
           String season = bufferedReader.readLine();

           System.out.println("On the weekend, or a weeknight?");
           String dayOfWeek = bufferedReader.readLine();

       }

       catch(IOException e)
       {
           e.printStackTrace();
       }
   }
}

Now that we can collect information from the user, let's define several booleans based on this information that we may later use for branching:

src/main/java/Hotel.java
import java.io.Console;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class Hotel {

   public static void main(String[] args) {


       try {
           BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
           System.out.println("In what season are you booking a stay?");
           String season = bufferedReader.readLine();

           System.out.println("On the weekend, or a weeknight?");
           String dayOfWeek = bufferedReader.readLine();

           boolean summer = season.equals("summer");
           boolean weekend = dayOfWeek.equals("weekend");

       }

       catch(IOException e)
       {
           e.printStackTrace();
       }
   }
}

Here, we've defined two booleans. Notice we're using the primitive type boolean, and not its wrapper class Boolean, because we're not calling any methods upon our booleans.

  • A summer boolean. It will be set to true if the season variable collected from the user is equal to the string "summer".
  • A weekend boolean. It will be true if the dayOfWeek variable collected from the user is equal to the string "weekend".

  • You may also notice we use the equals() method to compare the season variable to the string "summer", and the dayOfWeek variable to the string "weekend". This is because you cannot use == to compare strings in Java. It leads to errors. Instead, we must use equals(). We'll explore this in detail later on, but for now, simply know you cannot compare strings with ==.

Branching with Multiple Booleans Using &&

Next, let's return different statements to the user, based upon the combined values of these two booleans:

src/main/java/Hotel.java
public class Hotel {

   public static void main(String[] args) {


       try {
           BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
           System.out.println("In what season are you booking a stay?");
           String season = bufferedReader.readLine();

           System.out.println("On the weekend, or a weeknight?");
           String dayOfWeek = bufferedReader.readLine();

           boolean summer = season.equals("summer");
           boolean weekend = dayOfWeek.equals("weekend");

           if ( summer && weekend ) {
               System.out.println("Your stay is probably going to be pretty expensive. It's both peak travel season AND the weekend.");
           } else {
               System.out.println("Your stay might be expensive, but it's not during peak travel season, so it could certainly be worse!");
           }
       }

       catch(IOException e)
       {
           e.printStackTrace();
       }
   }
}

In our if statement we use the && operator. This operator checks whether both the statement to its left and its right are true. If so, the entire statement will evaluate to true. If one or both statements evaluate to false, the entire statement evaluates to false.

If both the summer and the weekend booleans are true, our program will tell the user, ""Your stay is probably going to be pretty expensive. It's both peak travel season AND the weekend."".

Complex Branching with else if

If we wanted to add even more complex situations, we could add the else if keyword.

src/main/java/Hotel.java
public class Hotel {
  public static void main(String[] args) {
    String season = "summer";
    String dayOfWeek = "weekend";

    boolean summer = season.equals("summer");
    boolean weekend = dayOfWeek.equals("weekend");

    if ( summer && weekend ) {
      System.out.println("Your stay is probably going to be pretty expensive. It's both peak travel season AND the weekend.");
    } else if ( summer ) {
      System.out.println("Your stay is probably going to be pretty expensive.");
    } else if ( weekend ) {
      System.out.println("Your stay is probably going to be pretty expensive.");
    } else {
      System.out.println("Your stay may be costly, but it could certainly be worse!");
    }
  }
}

In Java, we can use as many else if lines as necessary. Here, we still tell our user "Your stay is probably going to be pretty expensive. It's both peak travel season AND the weekend." if both the summer and dayOfWeek booleans are true, but we add additional circumstances using the else if keyword below.

  • If both summer and weekend are true, the application will still state "Your stay is probably going to be pretty expensive. It's both peak travel season AND the weekend." However, if both summer and weekend are not both true, our program will continue to work down the list.

  • If just summer is true, it will print "Your stay is probably going to be pretty expensive.". If it is not true, it the program will move on to the next statement.

  • If just weekend is true, it will print "Your stay is probably going to be pretty expensive.". If it is not true, the program will move on to the next statement.

  • If none of the previous conditionals are true, the program will hit the else statement, and print "Your stay may be costly, but it could certainly be worse!".

Maintaining DRY Branching with ||

You may have noticed that our two else if lines actually result in the same response to the user. We can DRY up our code by using the || operator. || means or. You can type this by hitting Shift + \ twice. The \ key is right above the enter key.

src/main/java/Hotel.java
public class Hotel {

   public static void main(String[] args) {

       try {
           BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
           System.out.println("In what season are you booking a stay?");
           String season = bufferedReader.readLine();

           System.out.println("On the weekend, or a weeknight?");
           String dayOfWeek = bufferedReader.readLine();

           boolean summer = season.equals("summer");
           boolean weekend = dayOfWeek.equals("weekend");

           if ( summer && weekend ) {
               System.out.println("Your stay is probably going to be pretty expensive. It's both peak travel season AND the weekend.");
           } else if ( summer || weekend ) {
               System.out.println("Your stay is probably going to be pretty expensive.");
           } else {
               System.out.println("Your stay may be costly, but it could certainly be worse!");
           }
       }

       catch(IOException e)
       {
           e.printStackTrace();
       }
   }
}

|| is very similar to && in that it evaluates two separate booleans. While && will only evaluate to true if both are true, || will evaluate to true if either of the booleans are true. Conversely, for || to evaluate to false, both booleans must be false.

  • In the code above, the first conditional continues to use the && operator. If both summer and weekend are true, it prints "Your stay is probably going to be pretty expensive. It's both peak travel season AND the weekend."

  • If both are not true, the program moves to the next conditional. This conditional now states else if ( summer || weekend ). If either weekend or summer are true, this conditional will print "Your stay is probably going to be pretty expensive.".

  • If neither conditional is true, we can assume both are false. In which case the program moves to the else statement, printing ""Your stay may be costly, but it could certainly be worse!".

The ! Operator

Finally, there is the ! operator, often called bang. It means "not". We can prepend this to any boolean, and it will reverse the value of that boolean. For example:

src/main/java/Hotel.java
public class Hotel {

   public static void main(String[] args) {


       try {
           BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(System.in));
           System.out.println("In what season are you booking a stay?");
           String season = bufferedReader.readLine();

           System.out.println("On the weekend, or a weeknight?");
           String dayOfWeek = bufferedReader.readLine();

           boolean summer = season.equals("summer");
           boolean weekend = dayOfWeek.equals("weekend");

           if ( !(summer && weekend) ) {
               System.out.println("You will likely get decent rates for your stay.");
           }

       }

       catch(IOException e)
       {
           e.printStackTrace();
       }
   }
}

Here, we're simply stating "if it's not the summer and the weekend...".

A note about if / else if / else

As you know, Java is renowned for its stability and reliability; once Java code is up and running, it rarely crashes. Many of the unique differences between JavaScript and Java are due to Java's pickiness which is one main reason why it is so stable.

In JavaScript, the following is completely acceptable and readable:

function burritoPriceChecker (price){

if (price < 7)  {
    console.log("buy it!");
    }
    else if (price > 7 && price < 12) {
    console.log("think about it - that's kinda expensive!");
    }
}

But what if someone decides to call this with a really high input: burritoPriceChecker(35);? The function doesn't contain any logic to handle this input as there is no else clause that handles the default. This will likely cause a crash or at least some strange behavior. Java won't allow you to write a method like this - it will require you to have an else if you have an else if to avoid runtime errors.

Switch Statements

Another way to write branching in Java is to use a switch statement, sometimes also called a switch/case or case/switch. Take a look at this example:

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

        int month = 8;
        String monthString;
        switch (month) {
            case 1:  monthString = "January";
                     break;
            case 2:  monthString = "February";
                     break;
            case 3:  monthString = "March";
                     break;
            case 4:  monthString = "April";
                     break;
            case 5:  monthString = "May";
                     break;
            case 6:  monthString = "June";
                     break;
            case 7:  monthString = "July";
                     break;
            case 8:  monthString = "August";
                     break;
            case 9:  monthString = "September";
                     break;
            case 10: monthString = "October";
                     break;
            case 11: monthString = "November";
                     break;
            case 12: monthString = "December";
                     break;
            default: monthString = "Invalid month";
                     break;
        }
        System.out.println(monthString);
    }
}

The principle is the same as with if/else, but the syntax is much shorter and more concise when there is a lengthy tree.

  • A condition is evaluated (month).
  • Java reads down the tree until it finds a true condition.
  • Once a true condition is located, it breaks out of the cascading tree.
  • If no true condition is found, the default is executed.
  • A default condition is required.