Java Flow Control: if and if-else Statements

Introduction

Conditional statements and loops are a very important tool in programming. There aren't many things we could do with code that can only execute line-by-line unconditionally.

That's what "flow control" means - guiding the execution of our program, instead of letting it execute line-by-line regardless of any internal or external factors. Every programming language supports some form of flow control, if not explicitly via ifs and fors, or similar statements - then it implicitly gives us the tools to create such constructs, i.e. low-level programming languages usually achieve that affect with a lot of go-to commands.

Loops were a concept used long before computer programming was even a thing, but the first person to use a software loop was Ada Lovelace, commonly known by her maiden name - Byron, while calculating Bernoulli numbers, back in the 19th century.

go-to commands are to be avoided in most situations, whenever we're using a programming language that gives us the tools to avoid it, like Java and all remotely similar languages do (there's a slight exception to this rule when breaking nested loops).

In Java, there are several ways to control the flow of the code:

  • if and if-else statements
  • switch statements
  • while and do-while statements
  • for and enhanced for statements
  • break and continue statements

The if Statement

The if statement is probably the most common form of flow control, regardless of the language. It's very simple and intuitive:

if (true) {
    // Execute this code block if the argument/expression is true
}

Since in a lot of cases, only a single line of code needs to be executed, you can skip using the curly brackets and simply indent the next line, though this only works for a single line:

if (true)
    System.out.println("Inside the if block");
    System.out.println("I have nothing to do with the if block!");
System.out.println("Neither do I!");

Typically, we don't directly input boolean values as the conditionals, like in these examples, but rather we use an expression:

if (username != null)
    System.out.println(username);
if (argument >= 10)
    System.out.println("The input is higher or equal than 10");

If the expression is false, the code belonging to the if statement is simply skipped:

if (1 > 5)
    System.out.println("Hello");
System.out.println(" World!");

And the output is:

 World!

The if-else Statement

Many times you don't want to just skip a piece of code if the expression is evaluated to be false. Typically, we want to do something else in that case:

if (expression) {
    // Code that executes only if the expression is true
}
else { // Optional
    // Code that executes only if the expression is false
}

Of course, you can use the else statement in conjunction with the shortened if statement:

if (expression)
    // Code that executes only if the expression is true
else
    // Code that executes only if the expression is false

However this is unrecommended to if you're not dealing with simple statements, especially with nested ifs since it's hard to figure out "which if does a particular else belong to". Using brackets will take ambiguity out of the problem if you find this approach unreadable.

The else statement statement is optional, so there's no need to write an empty else {} block if we don't want anything to happen if is the expression is false.

The else statement is intrinsically tied to the if statement and can't exist without it. It must appear right after the if statement otherwise an "else without if" compiler error will appear.

In many cases, we want to compare something for equality - whether one variable has the same value as another or whether it's smaller/larger than another. For example:

String s = "smol";

if (s.length() > 8)
    System.out.println("Variable s has too many characters!");
else
    System.out.println("Ok, so basically, s is very " + s);

The > operator here has the usual "greater than" meaning, and Java supports the usual group of relational operators. These include <, >, <=, =>, == checks for equality and != checks for inequality.

Note: Make sure to use == in if statements instead of =, or else you may assign a value instead of compare it.

2 < 5   // true
2 < 2   // false
2 <= 2  // true
2 == 3  // false
2 != 3  // true

Note: Most of the time, we don't compare String variables using == but with the .equals() method. == checks for object reference equality. For primitive types (int, char, etc.) it's the same as checking whether they have the same value, but with Strings it most commonly isn't.

if (string1.equals(string2))
    // Code to be executed if the strings are equal by value

Nested if Statements

We can have more than one if statement connected, either by nesting (putting one if statement inside another) or adding an else if at the end of our previous if:

int ourNumber = 10;

if (ourNumber > 8) {
    if (ourNumber % 2 == 0) {
       System.out.println("The number is larger than 8 and even");
    }
}
else if (ourNumber == 1) {
    System.out.println("1 is less than 8, so the previous if block isn't executed");
}
else {
    System.out.println("ourNumber is less than 8 and different than 1");
}

The else blocks always "belong" to the if of the same level, so the else if (ourNumber == 1) in our code belongs to the if (ourNumber > 8) and not the nested if that checks whether the number is even.

You can have as many else if blocks as you'd like, but only one else block, which must come last. Only the first of the if, else if and else blocks that evaluates to true will execute its code:

if (expression)
    // If expression is true, execute
else if (expression2)
    // If expression2 is true, execute
else if (expression3)
    // If expression3 is true, execute
...
else (expression4)
    // If expression4 is true, execute

Multiple Expressions in a Single if Statement

Another very useful thing is the ability to check multiple conditions in a single if:

int ourNumber = 10;

if (ourNumber > 5 && (ourNumber % 2 == 0)) {
    System.out.println("The number is larger than 8 and even");
}

The && operator is one of the logical operators Java supports. In the following examples boolExpr is used as shorthand for boolean expression. Reminder: a boolean expression is an expression that can be evaluated as true or false, i.e. it can also be an expression containing a logical operator, as we can see here:

  • !boolExpr: ! is negation, it evaluates a true expression to false and vice versa.
  • boolExpr1 & boolExpr2: the AND operator evaluates to true if both expressions are true.
  • boolExpr1 | boolExpr2: the OR operator evaluates to true if at least one of the expressions is true.
  • boolExpr1 && boolExpr2: the short-circuit AND operator evaluates to true only if boolExpr1 and boolExpr2 are both true. It's called a short-circuit operator because if the first expression is false, it won't even evaluate the second expression as the basic condition that both need to be true can never occur. With simple expressions, this doesn't affect the code in a meaningful way, but in production code, both expressions can be costly operations and this approach can significantly optimize code.
  • boolExpr1 || boolExpr2: the short-circuit OR operator evaluates to true if at least one of boolExpr1 and boolExpr2 is true. A short-circuit version of the | operator.
  • boolExpr1 ^ boolExpr2: the XOR operator evaluates to true only if boolExpr1 and boolExpr2 are evaluate as differently, i.e. if only one of them is true and the other one is false.
true && false // false
(2 < 5) && (2 != 3) // true
false || true //true
true && ((false || false) ^ true) // true
(true && false) || false // false

It's highly recommended to use parentheses whenever it makes sense to use them. The last example works the same without parentheses, but relying on operator precedence is extremely risky and leads to unreadable code.

Ternary Constructs

The only ternary operator (an operator that takes three operands) in Java is made up of the ? and : operators. It works like a very compact if-else statement.

// The if-statement below...
int max = 0;
if (a > b)
    max = a;
else max = b;

// ...can be written as:
int max = (a > b) ? a : b;

Here's how a ternary construct generally looks like:

(expression) ? returnValueIfTrue : returnValueIfFalse

Ternary constructs can work without parentheses as well, though it's generally more readable to use parentheses:

int max = a > b ? a : b;

You can also use ternary constructs to modify variables:

System.out.println("10% discount for orders above $50!");
double orderPrice = 55;
double finalPrice = orderPrice > 50 ? orderPrice*0.9 : orderPrice;
System.out.println("Your final price is $" + finalPrice);

And the output is:

Your final price is $49.5

Using the construct, we can also call methods:

public void someMethod() {
    String userRole = "Admin";
    String result = userRole.equals("Admin") ? greetAdmin() : greetUser();
    System.out.println(result);
}

static String greetAdmin() {
    return "Hello Admin!";
}

static String greetUser() {
    return "Hello User!";
}

Logically, this would result in:

Hello Admin!

Nested Ternary Constructs

System.out.println("10% discount for all orders above $50 and additional $5 off if your order exceeds 2 items!");
double orderPrice = 140;
int quantity = 5;
double finalPrice = (orderPrice > 50) ? (quantity > 2) ? (orderPrice*0.9)-5 : orderPrice*0.9 : orderPrice;
System.out.println("Your final price is: $" + finalPrice);

Since both conditions are true, the output is:

10% discount for all orders above $50 and additional $5 off if your order exceeds 2 items!
Your final price is: $121.0

However, this approach is horribly unreadable. If possible, avoid nesting ternary constructs, though if you must, break it down into simpler blocks with newlines and indentation. For an example, this example can be made a bit more readable:

System.out.println("10% discount for all orders above $50 and additional $5 off if your order exceeds 2 items!");
double orderPrice = 140;
int quantity = 5;
double finalPrice = (orderPrice > 50) ?
                        (quantity > 2) ?
                                (orderPrice*0.9)-5 : orderPrice*0.9
                    : orderPrice;
System.out.println("Your final price is: $" + finalPrice);

Conclusion

Flow control in code is essential for absolutely every application. Statements such as if and if-else are fundamental building blocks and every aspiring developer should be completely in control/aware of how they work.

If you'd like to read more about Flow Control in Java, we've got you covered!