Data Types and Variables
Mastering Control Flow
LEARNING OBJECTIVES
After this lesson, you will be able to:
- Use if/else if/else conditionals to control program flow based on boolean conditions
- Use switch conditionals to control program flow based on explicit conditions
- Use comparison operators to evaluate and compare statements
- Use boolean logic (!, &&, ||) to combine and manipulate conditionals
- Loop over a code block one or more times
STUDENT PRE-WORK
Before this lesson, you should already be able to:
- Create variables in Java using basic data types
- Use a text editor
INSTRUCTOR PREP
Before this lesson, instructors will need to:
- Open and run the starter and solution code
- Modify sections and checks as needed
Opening (5 mins)
From Culttt.com: "Control Flow Structures are an important aspect of programming languages that allow your code to take certain actions based on a variety of scenarios. Control Flow is a fundamental concept in programming that allows you to dictate how your code runs under different conditions or until a certain condition is met."
Introduction: Logical operators and control flow (10 mins)
Java supports a compact set of statements, specifically control flow statements, that you can use to incorporate a great deal of interactivity in your application.
Block Statements
Statements meant to be executed after a control flow operation will be grouped into what is called a block statement. These statements are wrapped into a pair of curly braces:
{
System.out.println("hello");
System.out.println("roar");
}
Block scope
We've seen that scope changes depending on whether a variable is defined in the class (we use the mVariableName
convention for these), or in a method (these variables have local scope only and are not available outside that method).
In Java, variables defined in block statements modify scope, meaning those variables are not available outside of the block.
Instructor Note: Show the scope error in the IDE, then correct it as shown in the second code block.
For example:
boolean beautiful = true;
if (beautiful)
{
String name = "jay";
}
System.out.println(name); // symbol 'name' cannot be resolved
Variables defined in block statements are not available outside of the curly braces. How might we resolve this issue?
Instructor note: first try to print out name without an else statement, then add the else, when the program still won't compile.
boolean beautiful = true;
String name = "pepe";
if (beautiful)
{
name = "robin"; // use the predefined variable
}
System.out.println(name);
//=> robin
Demo: Conditional statements (10 mins)
Conditional statements are a way of essentially skipping over a block of code if it does not pass a boolean expression. Java supports two conditional statements: if
...else
and switch
.
if...else statement
if(expr) { code }
... means run the code
block if expr
is true
if (1 > 0) {
System.out.println("hi");
}
//=> hi
When you need to test more than one case, you may use else if
:
String name = "kittens";
if (name.equals("puppies")) {
name += "!!!";
} else if (name.equals("kittens")) {
name += "!!";
} else {
name = "!" + name;
}
System.out.println(name);
//=> "kittens!!"
Ternary Operator
Java has a ternary operator for conditional expressions. You can think about the ternary operator as a concise "if-else in one line":
int age = 12;
String allowed = (age > 18) ? "yes" : "no";
System.out.println(allowed);
//=> "no"
Truth-y & False-y
It's important to know that all of the following become false
when converted to a Boolean:
Instructor Note: This snippet is optional.
false
0
""
(empty string)NaN
null
undefined
For example:
Boolean b = new Boolean("");
System.out.println(b);
//=> false
This can be vary helpful when checking if conditions exist, are undefined, or if variables don't hold value.
Guided Practice: Boolean/Logical Operators (15 mins)
Logical operators will always return a boolean value true
or false
.
There are two "binary" operators that require two values:
- AND, denoted
&&
- OR, denoted
||
A third "unary" operator requires only one value:
- NOT, denoted
!
&& (AND)
Do these with me!
The &&
operator requires both values to the left and right of the operator to be true
in order to return the entire statement as true
:
boolean result = false;
if(true && true) {
result = true;
}
System.out.println(result);
//=> true
Any other combination using the &&
operator is false
. What happens if I check true && false
?
boolean result = false;
if(true && false) {
result = true;
}
System.out.println(result);
//=> false
boolean result = false;
if(false && false) {
result = true;
}
System.out.println(result);
//=> false
|| (OR)
The ||
operator requires just one of the left or right values to be true
in order to return true.
So, now, if I do true || false
, what will be returned?
if(true || false) {
System.out.println("true");
}
//=> true
if(false || true) {
System.out.println("true");
}
//=> true
if(false || false) {
System.out.println("true");
}
//=> ... silence ...
Check: What is the only combination that will return false?
Only false || false
will return false
The !
takes a value and returns the opposite boolean value, i.e.
!(true)
//=> false
The &&
and ||
operators use short-circuit logic, which means whether they will execute their second operand is dependent on the first.
Check: Can you think of a time when this might be useful?
This is useful for checking for null objects before accessing their attributes:
if(instructor != null && instructor.getName().equals("drew")) {
System.out.println("davis")
}
In this case, if the first operand instructor != null
is false, then the second operand instructor.getName().equals("drew")
will not be evaluated. The expression is basically saying "we already know the whole &&
expression is false, because instructor != null
is false. Why bother dealing with the second operand?"
This is also important because a Null Pointer Exception
will be thrown if we try to call a method using "dot notation" on a null Object reference.
Demo: Comparison Operators (10 mins)
Comparisons in Java can be made on primitives using <
, >
, <=
, and >=
.
Check: What is a primitive data type?
'A' > 'a'
//=> false
'b' > 'a'
//=> true
12 > 12
//=> false
Note that you cannot do:
12 >= "12"
// or
"Apple" > "Oranges"
Equality Operator ==
Check: Can you remember from the pre-work how to compare variables?
When verifying equality between primitives use double equal ==
:
System.out.println(1 == 2);
=> false
But what about with Objects like strings?
A special note on Equality among Strings:
There are actually two ways to compare the equality of strings.
String blue = "blue";
boolean withSign = (blue == "blue"); //=> true
boolean withWords = (blue).equals("blue"); //=> true
Do you know which one of these would be preferred?
Well, lets do another example to show you which and why:
String blue = "blue";
String bl = "bl";
String ue = "ue";
System.out.println(bl+ue); //=> blue
boolean withSigns = (bl+ue == blue); //=> false
boolean withWords = (bl+ue).equals(blue); //=> true
Why isn't withSigns
true? The print out looks the same.
Well, Objects and arrays are complex collections of values, and when we refer to them, we're actually referencing where they live in memory. That's why we say Objects are passed by reference while things like ints and floats are passed by value.
==
compares the place where the object was stored on the computer.
What this means is that Java doesn't care if they look similar. It only compares whether or not they are the exact same object in memory. In each of the cases above, when checking for equality, we're actually comparing two objects that are in two different places in memory.
String blue
has a reference to where it is stored on the computer, and that is a different place than String bl
is stored. They're not exactly "the same" according to ==
.
equals()
, on the other hand, is a method that can be called on an instance(str1
) of a String Object. And this method checks whether the char
arrays in each String are the same, not whether the references are the same.
The long and short of it, use equals
when comparing strings.
Check: Why can we call methods on a variable with data type string but not on an int?
!=
There is also an !=
operator, which is the inverse ==
.
Introduction: Switch Statement (10 mins)
The switch statement can be used for multiple branches based on a number or string:
String food = "apple";
switch(food) {
case "pear":
System.out.println("I like pears");
break;
case "apple":
System.out.println("I like apples");
break;
default:
System.out.println("No favourite");
}
//=> I like apples
In this case, the switch
statement compares food
to each of the cases (pear
and apple
) and evaluates the expressions beneath them if there is a match. It uses String.equals
method in this case, or ==
in the case of primitives, to evaluate equality.
The default clause is optional.
Note: Breaks are important! If you don't put a break statement, the expression will continue to be evaluated for each following case. This might cause unintended consequences.
For example if you eliminate the break statements:
String food = "apple";
switch(food) {
case "pear":
System.out.println("I like pears");
case "apple":
System.out.println("I like apples");
default:
System.out.println("No favourite");
}
The result would be:
I like apples
No favourite
If food = "pear"
then the output would be:
I like pears
I like apples
No favourite
Thi is not exactly what we had intended.
Demo: Loops (15 mins)
Instructor Note: Students should follow along if they feel up to it.
In just about all programming languages, loop-ing exist. A loop is a statement or block of code that will continue to execute while or until a condition exists.
while
loops, for example, will run a block of code while a condition is true
.
Java has while
loops and do-while
loops.
The while
loop is good for basic looping, but there's a possibility it will never get run.
Check: In what case will the while loop never run?
while (true) {
// an infinite loop!
}
Using a do-while
loop makes sure that the body of the loop is executed at least once, because while()
isn't evaluated until after the block of code runs.
int input = 0;
do {
System.out.println(input++);
} while (input < 10);
You can use looping in combination with iteration: a way of incrementally repeating a task.
For example, using a for loop:
int iterations = 10;
for (int i = 0; i < iterations; i++) {
System.out.println(i);
}
Notice the placement of the comma and semi-colons, and let's take a look at what each of the parts do:
int i = 0;
is the initialization phase.- This is executed once, before the loop begins.
- Note that
int i
is declared within this phase. This means that the lifespan ofi
is limited to within the for loop, which is a much cleaner, and leads to less problems down the line.
i < iterations;
is the termination phase.- Every time the loop evaluates, it checks this statement.
- If this statement evaluates to
false
, the loop terminates. - This is equivalent to the
while
section of thedo...while
loop.
i++
is the increment expression.- This happens every time the loop evaluates.
- This is equivalent to the
do
section of thedo...while
loop. - In this case, each loop,
i
is incremented by 1.
In android studio, you can use fori+TAB
to automatically create an empty for loop
.
Fizz Buzz - Independent Practice (15 minutes)
Fizz buzz is a game about division. Create a program that will iterate through numbers from 1 to 101 and log each number in the console.
- In the loop every time a number is divisible by 3, instead of logging the number itself, the word "fizz" should appear.
- If the number is divisible by 5, the word "buzz" should be logged.
- If the number is divisible by both 3 and 5, then the word "fizzbuzz" should be logged.
Hint: Remember the modulus operator?
A typical output would look like this:
Solution
for (int i = 1; i < 101; i++) {
if((i % 3 == 0) && (i % 5 == 0)) {
System.out.println("fizzbuzz");
} else if(i % 3 == 0) {
System.out.println("fizz");
} else if(i % 5 == 0) {
System.out.println("buzz");
} else {
System.out.println(i);
}
}
Conclusion (5 mins)
These are some of the foundational tools you'll use in many of your applications. You'll probably need to refresh yourself on the exact syntax a few times before you memorize it, but it's important to be able to remember, these core "control flow" concepts, in general, because they'll come up in pretty much every programming language you'll ever encounter.