Java Full Course: Mastering the Language Decision Making and Branching

Decision Making and Branching

1. Introduction to Decision Making and Branching

A program is rarely a simple linear sequence of instructions. Most real-world applications need to evaluate conditions and choose different paths of execution based on the outcome—whether it’s validating user input, handling different business rules, or responding to runtime events. This ability to alter the flow of control is known as decision making and branching.

In Java, decision-making constructs allow a program to select one among several alternative blocks of code to execute. The choice is typically based on the evaluation of a boolean expression (e.g., a comparison, a logical condition, or a method that returns a boolean). Once the decision is made, the program "branches" to the appropriate section of code, optionally skipping other sections.

Java provides several statements to implement decision making:

  • if statement – executes a block of code if a condition is true.
  • if-else statement – chooses between two blocks based on a condition.
  • else-if ladder – selects one block from multiple possibilities.
  • switch statement – selects among several branches based on the value of an expression.
  • Nested control structures – embedding one decision construct inside another.
  • Ternary operator (already covered) – a compact way to embed a simple conditional expression.

Each of these constructs gives you the power to build logic that responds intelligently to data and user interactions. Understanding when to use if-else versus switch, how to structure complex conditions, and how to avoid common pitfalls like dangling else or fall through in switch is essential for writing clear, maintainable code.


2. The if Statement

The if statement is the most fundamental decision-making construct in Java. It allows a program to execute a block of code only when a specified condition evaluates to true. If the condition is false, the block is skipped, and execution continues with the next statement after the if.

2.1 Syntax

if (condition) { // block of code to execute if condition is true }
  • condition – a boolean expression (e.g., x > 5, isValid == true, or simply a boolean variable).
  • The block is a sequence of statements enclosed in curly braces {}. If only one statement is to be executed, the braces are optional, but it is a good practice to always use them.

2.2 Simple Example

int age = 20; if (age >= 18) { System.out.println("You are eligible to vote."); } System.out.println("Program continues...");

Output:

You are eligible to vote. Program continues...

If age were 16, the message inside the if would not be printed; only the second line would appear.


2.3 Flow of Control

Start | v condition? / \ true/ \false / \ v v execute skip block block | | | +----+------+ | v next statement

2.4 Using Blocks – Curly Braces

  • Always use braces {} even for a single statement. It improves readability and prevents errors when adding more statements later.

Without braces (works but risky):

if (x > 0) System.out.println("Positive"); System.out.println("This line is NOT part of the if!"); // Always executes

With braces (safe):

if (x > 0) { System.out.println("Positive"); System.out.println("This line is part of the if block"); }

2.5 Nested if Statements

An if statement can contain another if inside its block. This is called nesting.

int score = 85; if (score >= 60) { System.out.println("Passed"); if (score >= 90) { System.out.println("Excellent!"); } }

The inner if is evaluated only when the outer condition is true.


2.6 Common Pitfalls

a) Using = instead of ==

int x = 10; // if (x = 5) { } // Compilation error: int cannot be converted to boolean

The condition must be a boolean expression. Using = where == is intended often results in a compilation error unless the left side is a boolean variable.

b) Missing Braces Leading to Logic Errors

if (balance > 0) System.out.println("Balance positive"); System.out.println("Processing withdrawal..."); // Always executed!

The second statement is not part of the if block. Use braces to avoid such bugs.

c) Dangling else

Not an issue in Java because else always associates with the nearest preceding if that doesn’t already have an else. Use braces to clarify intent.

d) Null Checks

When dealing with objects, always check for null before accessing methods or fields to avoid NullPointerException.

String name = null; if (name != null && name.length() > 0) { // safe with short circuit // ... }

2.7 Best Practices

  • Always use braces {} even for single statements. This makes the code more maintainable and less error prone.
  • Keep conditions simple. Complex boolean expressions can be assigned to a well named variable first.
  • Use positive conditions where possible (e.g., if (isValid) rather than if (!isValid)).
  • Avoid deep nesting. If you need more than two or three levels, consider refactoring with methods or using other control structures.
  • Indent consistently to make the structure clear.

2.8 The if Statement Without Braces (Valid but Discouraged)

Java allows omitting braces when exactly one statement follows the if. This is a holdover from C/C++ but is generally discouraged because it can lead to subtle bugs.

if (flag) doSomething(); // one statement, no braces

When you later add another statement, forgetting to add braces will cause it to always execute.


2.9 if with Compound Conditions

The condition can be any boolean expression, including combinations with logical operators (&&, ||, !).

int age = 25; boolean hasLicense = true; if (age >= 18 && hasLicense) { System.out.println("You can drive"); }

Short circuit evaluation (&& and ||) ensures that the second operand is evaluated only when necessary.


2.10 Examples

Example 1: Simple comparison

int number = 7; if (number % 2 == 0) { System.out.println(number + " is even."); }

Example 2: Using a boolean variable

boolean isWeekend = true; if (isWeekend) { System.out.println("Relax!"); }

Example 3: Guarding against division by zero

int numerator = 10, denominator = 0; if (denominator != 0) { int result = numerator / denominator; System.out.println(result); } else { System.out.println("Cannot divide by zero."); }

Example 4: Checking string content (with null check)

String input = "hello"; if (input != null && input.equals("hello")) { System.out.println("Greeting received"); }

2.11 Key Points to Remember

  • The if statement executes its block only if the condition is true.
  • The condition must be a boolean expression.
  • Always use braces {} to define the block, even for a single statement.
  • Nested if statements are allowed, but keep nesting shallow.
  • Avoid the common mistake of using = instead of == for comparison.
  • Use short circuit operators to protect against null and expensive computations.
  • Keep conditions readable by extracting complex logic into named variables.

3. The if-else Statement

The if-else statement extends the basic if statement by providing an alternative block of code that executes when the condition is false. This allows a program to choose between two mutually exclusive paths.

3.1 Syntax

if (condition) { // block executed if condition is true } else { // block executed if condition is false }
  • The else block is optional but must immediately follow the if block (or another else-if).
  • Each block can contain zero or more statements. Braces are recommended even for single statements.

3.2 Simple Example

int age = 16; if (age >= 18) { System.out.println("You are eligible to vote."); } else { System.out.println("You are not yet eligible to vote."); }

Output: You are not yet eligible to vote.


3.3 Flow of Control

Start | v condition? / \ true/ \false / \ v v true block false block | | +-----+------+ | v next statement

Exactly one of the two blocks is executed; control then passes to the next statement after the if-else.


3.4 Nested if-else Statements

You can place if or if-else inside another if or else block.

int score = 85; if (score >= 60) { System.out.println("Passed"); if (score >= 90) { System.out.println("Grade: A"); } else { System.out.println("Grade: B"); } } else { System.out.println("Failed"); }

3.5 Dangling else Ambiguity

In Java, an else always associates with the nearest preceding if that does not already have an else. This is resolved by the compiler without ambiguity. However, proper indentation and braces make the intent clear.

Example without braces (risky):

if (x > 0) if (y > 0) System.out.println("Both positive"); else System.out.println("x is not positive"); // actually associates with inner if

The else belongs to the inner if, not the outer one. Always use braces to avoid confusion.


3.6 Multiple Statements in Blocks

Each block can contain any number of statements, including declarations (but variable scope is limited to the block).

if (discount > 0) { double finalPrice = price * (1 - discount / 100); System.out.println("Discounted price: " + finalPrice); } else { System.out.println("No discount applied"); System.out.println("Final price: " + price); }

Variables declared inside a block are not accessible outside it.


3.7 Common Pitfalls

a) Misplaced Semicolon

if (x > 0); // empty statement { System.out.println("x is positive"); // always executes }

b) Using = Instead of ==

if (flag = true) { ... } // compiles if flag is boolean, but always true

c) Unintended else Attachment

Without braces, the else may attach to the wrong if. Always use braces for clarity.


3.8 Best Practices

  • Always use braces {} for both if and else blocks.
  • Keep the logic simple.
  • Avoid deep nesting.
  • Order conditions logically.
  • Use meaningful variable names.

3.9 Using if-else with Boolean Variables

When the condition is already a boolean, you can write:

boolean isValid = true; if (isValid) { // ... } else { // ... }

Avoid redundant comparisons like if (isValid == true).


3.10 The Ternary Operator as an Alternative

For simple assignments, the ternary operator ? : can replace an if-else:

int max = (a > b) ? a : b;

3.11 Examples

Example 1: Determining if a number is positive, negative, or zero (with nesting)

int num = -5; if (num > 0) { System.out.println("Positive"); } else { if (num < 0) { System.out.println("Negative"); } else { System.out.println("Zero"); } }

Example 2: Checking leap year

int year = 2024; if ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)) { System.out.println("Leap year"); } else { System.out.println("Not a leap year"); }

Example 3: Guarding against null

String name = null; if (name != null) { System.out.println("Length: " + name.length()); } else { System.out.println("Name is null"); }

3.12 Key Points to Remember

  • The if-else statement executes one of two blocks based on a boolean condition.
  • Exactly one block (the if block or the else block) is executed.
  • Braces are optional for single statements but highly recommended.
  • An else always attaches to the nearest preceding if.
  • Variables declared inside a block are local to that block.

4. The else-if Ladder

The else-if ladder (multi-way branching) is used when a program must choose among several mutually exclusive alternatives.

4.1 Syntax

if (condition1) { // block executed if condition1 is true } else if (condition2) { // block executed if condition1 is false and condition2 is true } else { // block executed if none of the above conditions is true }
  • The chain can have any number of else if blocks.
  • The final else is optional but recommended.
  • Once a condition is found to be true, the rest of the ladder is skipped.

4.2 Flow of Control

Only the first matching block (or the final else block) is executed; control then exits the entire ladder.


4.3 Simple Example

int score = 85; if (score >= 90) { System.out.println("Grade: A"); } else if (score >= 80) { System.out.println("Grade: B"); } else if (score >= 70) { System.out.println("Grade: C"); } else { System.out.println("Grade: F"); }

4.4 Characteristics

  • Mutual Exclusion: Only one block can execute.
  • Order Matters: The first true condition wins. Arranging from most specific to most general is crucial.

4.5 Comparison with Nested if-else

An else-if ladder is essentially a flattened version of nested if-else, making it more readable and avoiding excessive indentation.


4.6 Common Pitfalls

  • Overlapping Conditions: Ensure conditions are mutually exclusive when that is the intent.
  • Redundant Conditions: Simplify conditions that are already covered by previous ones.
  • Forgetting Braces: Always use braces to define the blocks.

4.7 Best Practices

  • Order conditions from most specific to most general.
  • Limit the number of branches (consider switch or polymorphism for many branches).
  • Always include a final else to catch unexpected values.
  • Keep the blocks short; extract to methods if they become complex.

4.8 Examples

Example: Categorizing age groups

int age = 25; if (age < 13) { System.out.println("Child"); } else if (age < 20) { System.out.println("Teenager"); } else if (age < 65) { System.out.println("Adult"); } else { System.out.println("Senior"); }

Example: Commission calculation

double sales = 12500; double commission; if (sales > 10000) { commission = sales * 0.15; } else if (sales > 5000) { commission = sales * 0.10; } else { commission = 0; }

4.9 else-if vs. switch

Use else-if for conditions involving ranges or complex boolean logic. Use switch when comparing one variable against discrete equality checks.


4.10 Key Points to Remember

  • The else-if ladder tests multiple conditions in sequence.
  • The first true condition executes its block, and the rest are skipped.
  • Order matters: evaluate top to bottom.
  • Use braces {} to avoid logic errors.

5. The switch Statement

The switch statement is a multi-way branching construct that allows a variable or expression to be tested for equality against a list of values (called cases). It provides a cleaner and often more efficient alternative to long else-if ladders when the decision is based on a single discrete value.


5.1 Traditional switch Syntax (Pre Java 12)

switch (expression) { case value1: // code to execute when expression == value1 break; case value2: // code to execute when expression == value2 break; // ... more cases default: // code to execute when no case matches break; }
  • expression – must evaluate to a byte, short, char, int, String (Java 7+), or an enum.
  • case value – a constant expression (compile-time constant) of a type compatible with the expression.
  • break – exits the switch block; without it, execution "falls through" to the next case.
  • default – optional; executes if no case matches. Can appear anywhere, but conventionally at the end.

5.2 Enhanced switch (Java 12–14)

Java 12 introduced preview features that became standard in Java 14: arrow syntax (->) and yield for returning values.

a) Arrow Syntax (->) – No Fall-Through

switch (expression) { case value1 -> // single expression or block case value2 -> { // multiple statements } default -> // default action }
  • No break needed; only the matched case executes.
  • On the right side, you can use a single expression or a block { ... }.
  • Multiple constants per case are allowed: case value1, value2 -> ....

b) switch as an Expression (using yield)

Starting with Java 14, switch can be used as an expression that returns a value.

int result = switch (day) { case MONDAY, FRIDAY -> 5; case TUESDAY -> 4; case WEDNESDAY -> 3; default -> { int val = computeDefault(); yield val; // yield returns the value from the switch expression } };
  • Every branch must produce a value, and the types must be compatible.
  • yield is used to return a value from a block.

5.3 Fall-Through in Traditional switch

If you omit break, execution continues into the next case. This is called "fall-through".

int day = 2; switch (day) { case 1: System.out.println("Monday"); case 2: System.out.println("Tuesday"); case 3: System.out.println("Wednesday"); break; default: System.out.println("Other"); } // Output: Tuesday // Wednesday

Fall-through can be useful when multiple cases share the same logic:

switch (month) { case 4: case 6: case 9: case 11: days = 30; break; case 2: days = 28; break; default: days = 31; }

5.4 Supported Types in switch

TypeSupported Since
byteJava 1.0
shortJava 1.0
charJava 1.0
intJava 1.0
enumJava 5
StringJava 7
Wrappers (Byte, Short, Character, Integer)Java 1.0 (auto-unboxing works)
boolean, long, float, doubleNot allowed directly

5.5 Case Labels

  • Must be compile-time constants (literals, final variables initialized with constants, enum constants).
  • Cannot be null for String cases.
  • Cannot be duplicate values.
final int CONST = 10; switch (value) { case 5: case CONST: // allowed because CONST is constant case 5 + 5: // allowed, evaluates to 10 }

5.6 switch with Strings

String fruit = "apple"; switch (fruit) { case "apple": System.out.println("Apple pie"); break; case "banana": System.out.println("Banana split"); break; default: System.out.println("Unknown fruit"); }

The String comparison is case-sensitive and uses equals() internally.


5.7 switch with Enums

enum Day { MON, TUE, WED, THU, FRI, SAT, SUN } Day today = Day.MON; switch (today) { case MON: case TUE: case WED: case THU: case FRI: System.out.println("Weekday"); break; case SAT: case SUN: System.out.println("Weekend"); break; }

When using enums, you don't need to qualify the constants with the enum name inside the switch.


5.8 Using yield in Switch Expressions (Java 14+)

Switch expressions must cover all possible values or include a default. The yield keyword returns a value from a block.

int numLetters = switch (day) { case MONDAY, FRIDAY, SUNDAY -> 6; case TUESDAY -> 7; case THURSDAY, SATURDAY -> 8; case WEDNESDAY -> 9; default -> { System.out.println("Invalid day"); yield -1; } };

5.9 Common Pitfalls

a) Missing break (Unintended Fall-Through)

switch (x) { case 1: System.out.println("One"); case 2: System.out.println("Two"); }

If x == 1, both lines print. This is often a bug. Use the enhanced syntax or add break deliberately.

b) Not Handling All Cases

If you omit default and none of the cases match, the switch does nothing. It's a good practice to include a default for unexpected values.

c) Using Non-Constant Expressions in case

int a = 5, b = 10; switch (x) { case a + b: // Not allowed unless a+b is a constant expression }

d) Using Unsupported Types

long l = 10L; switch (l) { } // Compilation error

e) Duplicate Cases

switch (x) { case 1: case 1: // duplicate, compilation error }

f) Using null in switch with String or Enum

String s = null; switch (s) { // NullPointerException at runtime case "A": break; }

Always check for null before switching on a reference type.


5.10 switch vs. if-else

Featureswitchif-else Ladder
ConditionSingle expression with discrete equalityArbitrary boolean expressions
Use caseMultiple fixed values, ranges via fall-throughRanges, complex logic, boolean conditions
ReadabilityClean for many discrete valuesCan become verbose for many values
PerformanceCan be optimized (jump table) for integral typesLinear evaluation of conditions
Type supportLimited to integral, String, enumAny boolean expression
Fall-throughPossible (traditional syntax)Not applicable

Use switch when you have one variable being compared to many constant values. Use if-else for ranges, conditions involving multiple variables, or non-equality checks.


5.11 Best Practices

  • Prefer enhanced switch (->) in Java 14+ to avoid accidental fall-through and to make code more concise.
  • Always include a default branch unless you are absolutely sure all possible values are covered (e.g., with enums, consider adding default to handle future enum constants).
  • Use yield for switch expressions when you need to return a value.
  • Check for null before switching on String or enum to avoid NullPointerException.
  • Keep case blocks short; if a case requires many lines, extract into a method.
  • Use switch over if-else when you have 3 or more discrete cases for better readability.

5.12 Examples

Example 1: Traditional with fall-through for shared logic

int month = 2; int days; switch (month) { case 4: case 6: case 9: case 11: days = 30; break; case 2: days = 28; break; default: days = 31; } System.out.println("Days: " + days);

Example 2: Enhanced switch with arrow and multiple constants

String dayType = switch (dayOfWeek) { case "MON", "TUE", "WED", "THU", "FRI" -> "Weekday"; case "SAT", "SUN" -> "Weekend"; default -> "Invalid"; };

Example 3: Switch expression with yield and block

int result = switch (operation) { case "ADD" -> { int sum = a + b; yield sum; } case "SUB" -> a - b; default -> { System.out.println("Unknown operation"); yield 0; } };

Example 4: Using enum in enhanced switch

enum TrafficLight { RED, YELLOW, GREEN } String action = switch (light) { case RED -> "Stop"; case YELLOW -> "Caution"; case GREEN -> "Go"; };

5.13 Key Points to Remember

  • switch tests a single expression against multiple constant values.
  • Traditional switch requires break to prevent fall-through; enhanced switch uses -> which does not fall through.
  • Supported types: byte, short, char, int, String, enum, and their wrappers.
  • switch can be used as a statement (no return) or as an expression (returns a value) in Java 14+.
  • default is optional but recommended.
  • Case labels must be compile-time constants and cannot be null.
  • Always handle null separately when switching on String or enum.
  • For complex conditions or ranges, prefer if-else.

Hi! Need help with studies? 👋
AI