When you closed module 1 you left BiblioTechApp working, but with an uncomfortable diagnosis: it does not repeat, it does not decide, it does not validate and it remembers nothing. This module attacks the first three problems, and this lesson deals with the most urgent one: deciding. Until now your program was a straight line —read, calculate, print— and any "decision" it made was really an arithmetic trick or a ternary operator buried inside a formula. If an employee typed -5 elapsed days, the program accepted it without blinking. If the delay was 200 days, it printed the same sentence as if it were 1. Conditional statements are the mechanism a program uses to fork its path: it runs some instructions or others depending on the state of the data. Without them there are no business rules, no validation and no software; with them, BiblioTechApp starts behaving like a real application.
Contents
- From a linear program to a program that decides
- The simple
if - The
{}block and why omitting it is dangerous if-else: the two branches- The
else ifladder - Compound conditions and short-circuiting in practice
- Nesting, guard clauses and early exit
if-elseversus the ternary operator- Comparing
Strings in conditions and thenulltrap - The BiblioTech business rules as a decision table
- Validating user input with
if - Common Mistakes and Tips
- Exercises
- From a linear program to a program that decides
A program without conditionals runs all its instructions, always, in the same order. That is what BiblioTechApp does today:
flowchart TD
A["Read data"] --> B["Calculate delay"]
B --> C["Calculate fine"]
C --> D["Print receipt"]
A program with conditionals has alternative paths. The same journey, but with detours:
flowchart TD
A["Read data"] --> V{"Data valid?"}
V -- "no" --> E["Report the error"]
V -- "yes" --> B["Calculate delay"]
B --> R{"Is it late?"}
R -- "no" --> P["Receipt with no fine"]
R -- "yes" --> M["Calculate fine"]
M --> P
The difference is enormous: the number of possible paths is no longer one. And with it comes a new responsibility that will stay with you for the rest of the course: every branch you write is a branch that has to be tested.
- The simple
if
ifThe most basic form runs a block only if a boolean condition is true.
Points to internalise from day one:
- The condition always goes in parentheses.
- The condition must be of type
boolean. There is no implicit conversion: in Java,if (1)does not compile (unlike C or JavaScript). This eliminates a whole family of bugs. - After the
)you do not put a semicolon.if (x > 0);compiles, but theifends up empty and the following block always runs.
Applied to BiblioTech:
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final int LOAN_DAYS = 15;
final double DAILY_RATE = 0.25;
String title = "Effective Java";
int elapsedDays = 27;
int daysLate = elapsedDays - LOAN_DAYS; // 12
System.out.println("Book: " + title);
if (daysLate > 0) {
double fine = daysLate * DAILY_RATE;
System.out.printf("Returned after the deadline. Fine: %.2f EUR%n", fine);
}
System.out.println("Record completed.");
}
}Notice three things:
daysLate > 0produces aboolean; that is the only requirement.- The
finevariable is declared inside the{}block, so it only exists in there. Outside theifyou cannot use it: the compiler will saycannot find symbol. A variable's scope is the block in which it is declared. "Record completed."is always printed, fine or no fine: it is outside theif.
- The
{} block and why omitting it is dangerous
{} block and why omitting it is dangerousJava lets an if govern a single statement without braces:
This compiles and works. And it is such a well-known source of bugs that most professional style guides (Google's, Oracle's, almost any company's) forbid it. The reason:
if (daysLate > 0)
System.out.println("Overdue");
System.out.println("Applying fine..."); // ALWAYS runs!The indentation suggests both lines depend on the if, but Java ignores indentation. Only the first statement belongs to the if; the second one always runs, even with daysLate equal to 0. This mistake was the origin of Apple's "goto fail" security bug in 2014, which affected millions of devices.
Course rule: always use braces, even for a single line. The cost is two characters; the benefit is that adding one more line will never change the meaning of the code.
if-else: the two branches
if-else: the two branchesWhen there is an explicit alternative, else picks up every case in which the condition is false:
if (daysLate > 0) {
System.out.println("Return AFTER THE DEADLINE");
} else {
System.out.println("Return ON TIME");
}if and else are mutually exclusive and exhaustive: exactly one of the two branches runs, never neither and never both. That guarantee is very valuable: if you need a variable to end up initialised whatever happens, an if-else assures it and the compiler knows it.
String status; // declared with no value
if (daysLate > 0) {
status = "OVERDUE";
} else {
status = "ON TIME";
}
System.out.println(status); // compiles: the compiler knows it is always assignedIf you removed the else, the compiler would give the error variable status might not have been initialized, because there would be a path where the variable arrived without a value. The Java compiler analyses the possible paths; it is worth having it as an ally.
- The
else if ladder
else if ladderWhen there are more than two cases, conditions are chained. Technically there is no elseif keyword: what exists is an if inside the previous else, written compactly.
if (condition1) {
// case 1
} else if (condition2) {
// case 2
} else if (condition3) {
// case 3
} else {
// none of the above
}Behaviour rules you must memorise:
- The conditions are evaluated top to bottom and it stops at the first true one.
- Only one block runs, even if several conditions are true.
- The final
elseis optional; without it, it is possible that no branch runs at all.
That is why the order matters. This ladder is wrong:
// INCORRECT: the first case swallows all the others
if (daysLate > 0) {
System.out.println("Minor delay");
} else if (daysLate > 7) {
System.out.println("Severe delay"); // unreachable in practice
}With daysLate = 20, the first condition is already true and "Minor delay" is printed. The second branch is never reached with values greater than 7. The practical rule: order from the most restrictive condition to the most general one.
// CORRECT
if (daysLate > 7) {
System.out.println("Severe delay");
} else if (daysLate > 0) {
System.out.println("Minor delay");
} else {
System.out.println("No delay");
}
- Compound conditions and short-circuiting in practice
In module 1 you saw &&, ||, ! and short-circuiting. Here you use them for real. A one-line reminder: && evaluates the right operand only if the left one is true; || evaluates it only if the left one is false.
That turns the order of the operands into a protection tool:
String reference = null;
// The order saves the program: if reference is null, isEmpty() is never called.
if (reference != null && !reference.isEmpty()) {
System.out.println("Reference: " + reference);
}If you wrote the conditions the other way round (!reference.isEmpty() && reference != null), the program would try to call a method on null and blow up. That protection has its own name: a null guard.
The same principle applied to BiblioTech, where a loan is only considered "recoverable with a notice" if it is late but the cap has not yet been reached:
double fine = daysLate * DAILY_RATE;
if (daysLate > 0 && fine < MAX_FINE) {
System.out.println("Partial fine: the cap has not been reached yet.");
}
if (daysLate == 0 || elapsedDays <= LOAN_DAYS) {
System.out.println("Loan closed with no incidents.");
}And ! to negate, with one piece of advice: one negation is readable, two are a riddle. Instead of if (!(daysLate <= 0)), write if (daysLate > 0).
| Written like this | Better like this | Reason |
|---|---|---|
if (!(a > b)) |
if (a <= b) |
The negation of a comparison is another comparison |
if (!(a && b)) |
if (!a || !b) |
De Morgan's law; often clearer still to reframe the domain |
if (isValid == true) |
if (isValid) |
Comparing a boolean with true is redundant |
if (isValid == false) |
if (!isValid) |
Likewise |
- Nesting, guard clauses and early exit
Nesting conditionals means putting an if inside another. It works, but it quickly degenerates into so-called "arrow code", in which the logic drifts to the right and becomes unreadable:
// Hard to follow: you have to hold three conditions in your head at once
if (employee != null) {
if (!employee.isEmpty()) {
if (elapsedDays >= 0) {
if (elapsedDays > LOAN_DAYS) {
System.out.println("Process fine");
}
}
}
}The technique that fixes it is called a guard clause: instead of nesting the good case, you discard the bad cases first and exit. Since in this module all the code lives in main and you have not seen methods yet (they arrive in lesson 03-03), the exit is done with return;, which in main ends the program.
if (employee == null || employee.isEmpty()) {
System.err.println("ERROR: the employee name is mandatory.");
return; // ends main
}
if (elapsedDays < 0) {
System.err.println("ERROR: the elapsed days cannot be negative.");
return;
}
// From here on, everything that follows is the "happy path", with no extra indentation.
if (elapsedDays > LOAN_DAYS) {
System.out.println("Process fine");
}Compare both styles:
| Aspect | Deep nesting | Guard clauses |
|---|---|---|
| Indentation | Grows with each condition | Stays flat |
| Mental load | You have to hold every condition in your head | Each guard is forgotten as soon as you pass it |
| Error messages | Hard to tie to their cause | One per guard, specific |
| Main path | Buried at the bottom | Visible at the end, with no noise |
When in module 3 you learn to write methods, this pattern will be even more natural: each guard will return from the method without aborting the whole program. The idea, however, is the same and it is worth adopting now.
if-else versus the ternary operator
if-else versus the ternary operatorYou already used the ternary condition ? valueIfTrue : valueIfFalse in module 1 to clamp the delay to zero. It is worth pinning down when to use each tool, because they are interchangeable only in appearance.
The ternary is an expression: it produces a value. The if is a statement: it runs actions.
// Ternary: choosing a VALUE. Correct and readable.
int daysLate = (elapsedDays > LOAN_DAYS) ? elapsedDays - LOAN_DAYS : 0;
String status = (daysLate > 0) ? "OVERDUE" : "ON TIME";
// if-else: running ACTIONS. The ternary would be no use here.
if (daysLate > 0) {
System.out.println("Notifying the employee...");
System.out.printf("Amount: %.2f EUR%n", fine);
} else {
System.out.println("Nothing to charge.");
}| Criterion | Use a ternary | Use if-else |
|---|---|---|
| Goal | Obtaining a value | Running instructions |
| Number of branches | Exactly 2 | 2 or more |
| Length | Fits comfortably on one line | Several statements per branch |
| Nesting | Never more than one | No problem |
Initialising a final variable |
Ideal | Possible, but more verbose |
The practical limit is nesting. This is valid but indefensible in a code review:
// UNREADABLE: do not do this
String severity = daysLate == 0 ? "NONE" : daysLate <= MINOR_THRESHOLD ? "MINOR" : "SEVERE";The same logic with a ladder reads effortlessly, and it is what you will use in BiblioTech:
String severity;
if (daysLate == 0) {
severity = "NONE";
} else if (daysLate <= MINOR_THRESHOLD) {
severity = "MINOR";
} else {
severity = "SEVERE";
}
- Comparing
Strings in conditions and the null trap
Strings in conditions and the null trapHere is the number one mistake for anyone coming from other languages. In Java, == on objects compares references (whether they are the same object in memory), not content. With String it works sometimes —because of the literal pool you saw in module 1— and fails exactly when the text comes from the user, which is always.
String option = scanner.nextLine(); // the user types: exit
if (option == "exit") { // WRONG: almost always false
System.out.println("Goodbye");
}
if (option.equals("exit")) { // RIGHT: compares content
System.out.println("Goodbye");
}Useful methods in conditions:
| Method | What it checks | true example |
|---|---|---|
a.equals(b) |
Identical content, case-sensitive | "exit".equals("exit") |
a.equalsIgnoreCase(b) |
Identical content ignoring case | "Exit".equalsIgnoreCase("EXIT") |
a.isEmpty() |
Zero length | "".isEmpty() |
a.isBlank() |
Empty or only spaces (Java 11+) | " ".isBlank() |
a.startsWith(b) |
Starts with that text | "978-0000000001".startsWith("978") |
a.contains(b) |
Contains that text | "Effective Java".contains("Java") |
For user input, equalsIgnoreCase combined with trim() avoids absurd complaints:
String answer = scanner.nextLine().trim();
if (answer.equalsIgnoreCase("yes")) {
System.out.println("Confirmed.");
}The null trap. If a String variable is null, calling any method on it stops the program with a NullPointerException (you will see how to handle it in module 6; for now, avoid it). The standard technique has two forms:
String reference = null;
// Option A: explicit guard with short-circuiting
if (reference != null && reference.equals("REF-001")) { ... }
// Option B (more elegant): the literal on the left, which is never null
if ("REF-001".equals(reference)) { ... } // false, without blowing upOption B is known as a Yoda comparison. "REF-001" is a literal, it is never null, and equals cleanly returns false when it receives null as an argument. Use it when the value may be null and you do not want to write the guard.
- The BiblioTech business rules as a decision table
Before writing conditionals it is worth writing the decision table: which cases exist, how they are told apart and what each one does. If the table is right, the code writes itself.
Rules of the Nexus Software library (constants already fixed in module 1: LOAN_DAYS = 15, DAILY_RATE = 0.25, MAX_FINE = 20.0, MINOR_THRESHOLD = 7):
| Case | Condition | status |
severity |
Fine |
|---|---|---|---|---|
| 1. On time | daysLate == 0 |
ON TIME |
NONE |
€0.00 |
| 2. Minor delay | daysLate <= MINOR_THRESHOLD |
OVERDUE |
MINOR |
daysLate * 0.25 |
| 3. Severe delay | daysLate > MINOR_THRESHOLD and fine < cap |
OVERDUE |
SEVERE |
daysLate * 0.25 |
| 4. Capped fine | calculated fine >= MAX_FINE |
OVERDUE |
MAXIMUM |
€20.00 |
Notice that case 4 cuts across cases 2 and 3: it does not depend on the days but on the amount. That is why it is checked after calculating the fine, not before. The complete flow:
flowchart TD
A["daysLate = max(0, elapsedDays - LOAN_DAYS)"] --> B{"daysLate == 0"}
B -- "yes" --> C["status = ON TIME<br/>severity = NONE<br/>fine = 0"]
B -- "no" --> D["fine = daysLate * DAILY_RATE"]
D --> E{"fine >= MAX_FINE"}
E -- "yes" --> F["fine = MAX_FINE<br/>severity = MAXIMUM"]
E -- "no" --> G{"daysLate <= MINOR_THRESHOLD"}
G -- "yes" --> H["severity = MINOR"]
G -- "no" --> I["severity = SEVERE"]
C --> Z["Print receipt"]
F --> Z
H --> Z
I --> Z
And the direct translation into code, replacing module 1's nested ternaries:
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final int LOAN_DAYS = 15;
final double DAILY_RATE = 0.25;
final double MAX_FINE = 20.0;
final int MINOR_THRESHOLD = 7;
String employee = "Diego Alonso";
String title = "Design Patterns";
String isbn = "978-0000000002";
int elapsedDays = 120;
// Delay clamped to zero: never negative.
int daysLate = elapsedDays - LOAN_DAYS;
if (daysLate < 0) {
daysLate = 0;
}
String status;
String severity;
double fine;
if (daysLate == 0) {
status = "ON TIME";
severity = "NONE";
fine = 0.0;
} else {
status = "OVERDUE";
fine = daysLate * DAILY_RATE;
if (fine >= MAX_FINE) {
fine = MAX_FINE; // the cap is applied
severity = "MAXIMUM";
} else if (daysLate <= MINOR_THRESHOLD) {
severity = "MINOR";
} else {
severity = "SEVERE";
}
}
System.out.println("=== BiblioTech - Nexus Software ===");
System.out.printf("%-14s %s%n", "Employee:", employee);
System.out.printf("%-14s %s (%s)%n", "Book:", title, isbn);
System.out.printf("%-14s %d%n", "Days late:", daysLate);
System.out.printf("%-14s %s / %s%n", "Status:", status, severity);
System.out.printf("%-14s %.2f EUR%n", "Fine:", fine);
// Extra notice: only when the fine has reached the cap.
if (severity.equals("MAXIMUM")) {
System.out.println();
System.out.println("WARNING: fine capped. Notify Human Resources.");
}
}
}Output with elapsedDays = 120 (105 days late, theoretical fine €26.25):
=== BiblioTech - Nexus Software === Employee: Diego Alonso Book: Design Patterns (978-0000000002) Days late: 105 Status: OVERDUE / MAXIMUM Fine: 20.00 EUR WARNING: fine capped. Notify Human Resources.
One design detail worth pointing out: the three variables status, severity and fine are declared before the if and assigned inside it. Since every branch ends up assigning them, the compiler accepts using them afterwards. If you removed the final else, it would stop compiling. That is the compiler verifying your decision table.
- Validating user input with
if
ifThe module 1 program accepted anything. Now you can reject what makes no sense. The strategy, with guard clauses, is to read and check immediately:
package com.nexussoftware.bibliotech;
import java.util.Scanner;
public class BiblioTechApp {
public static void main(String[] args) {
final int LOAN_DAYS = 15;
Scanner scanner = new Scanner(System.in);
System.out.print("Employee: ");
String employee = scanner.nextLine().trim();
if (employee.isBlank()) {
System.err.println("ERROR: the employee name cannot be empty.");
scanner.close();
return;
}
System.out.print("Book title: ");
String title = scanner.nextLine().trim();
if (title.isBlank()) {
System.err.println("ERROR: the title cannot be empty.");
scanner.close();
return;
}
System.out.print("ISBN: ");
String isbn = scanner.nextLine().trim();
if (!isbn.startsWith("978")) {
System.err.println("ERROR: the library ISBN must start with 978.");
scanner.close();
return;
}
System.out.print("Elapsed days: ");
String daysInput = scanner.nextLine().trim();
int elapsedDays = Integer.parseInt(daysInput);
if (elapsedDays < 0) {
System.err.println("ERROR: the elapsed days cannot be negative.");
scanner.close();
return;
}
if (elapsedDays > 3650) {
System.err.println("ERROR: implausible value (more than 10 years).");
scanner.close();
return;
}
System.out.println("Input validated. Processing the return for " + employee + "...");
int daysLate = elapsedDays - LOAN_DAYS;
if (daysLate < 0) {
daysLate = 0;
}
System.out.println("Days late: " + daysLate);
scanner.close();
}
}Look at what you have just achieved: the program no longer accepts negative days, nor empty names, nor ISBNs from another library. And pause on the obvious limitation: when it detects an error, the program gives up and ends. The sensible thing would be to ask for the value again. That requires repeating, and repeating is exactly what the loops in the next lesson do.
There is one hole you cannot plug yet: if the user types twenty instead of 20, Integer.parseInt does not fail through an if, it stops the program with a NumberFormatException. No condition can prevent it, because the error happens inside the conversion. That gap is closed in module 6, with exceptions. For now, assume the user types digits.
Common Mistakes and Tips
1. The phantom semicolon.
It compiles with no warnings and is fiendishly hard to spot. No modern IDE lets it pass without underlining it: pay attention.
2. Confusing = with ==.
boolean available = false;
if (available = true) { ... } // ASSIGNS true and then evaluates true: always entersWith booleans it compiles and produces a silent bug. With other types (if (x = 5)) it does not compile, because int is not boolean. Write plain if (available) and the problem disappears.
3. Comparing Strings with ==. You have already seen it: use equals or equalsIgnoreCase. Always.
4. Comparing doubles with ==. Because of the binary precision you saw in module 1, 0.1 + 0.2 == 0.3 is false. For amounts, compare with a tolerance or —better— compare the integers they derive from:
5. Ladders with overlapping, badly ordered conditions. Remember: from the most restrictive to the most general. And check that every branch is reachable with some real data.
6. Forgetting the final else. If your branches assign a variable, without an else it may end up unassigned. The compiler will warn you; do not silence it by initialising to a fake value like "" or -1 without thinking, because then the bug moves to runtime.
7. Style tip: extract complex conditions into named boolean variables.
// Before: you have to decipher it
if (daysLate > MINOR_THRESHOLD && fine < MAX_FINE && !employee.isBlank()) { ... }
// After: it reads like a sentence
boolean isSevereDelay = daysLate > MINOR_THRESHOLD;
boolean fineBelowCap = fine < MAX_FINE;
boolean validEmployee = !employee.isBlank();
if (isSevereDelay && fineBelowCap && validEmployee) { ... }There is no appreciable performance cost and the code documents itself. Besides, when debugging (lesson 02-05) you will see each condition separately.
8. Tip: test the boundaries. If your condition is daysLate <= MINOR_THRESHOLD, test with 6, 7 and 8. Conditional bugs almost always live on the exact edge.
Exercises
Exercise 1: availability classifier
Write a program that, given the variables title, available (boolean) and daysRemaining (int, days left before the current loan expires; it is 0 if the book is on the shelf), prints one of these messages:
- If
availableistrue:"Effective Java: AVAILABLE for immediate loan". - If it is not available and
daysRemainingis greater than 0:"Effective Java: ON LOAN, back in N days". - If it is not available and
daysRemainingis 0 or negative:"Effective Java: ON LOAN AND OVERDUE, chase the employee".
Use an else if ladder and braces everywhere. Test the three cases.
Exercise 2: validating a new loan
An employee wants to take a book. The Nexus Software library only authorises the loan if all of the following hold:
- The employee's name is not blank.
- The book is available (
available == true). - The employee has no outstanding fines (
outstandingFine == 0.0). - The employee's number of active loans (
activeLoans) is fewer than 3.
Write the program with guard clauses: each failure prints a specific message on System.err and ends with return. If everything holds, print the authorisation with the return date expressed as "Return in 15 days" using the LOAN_DAYS constant.
Test it with Nuria Vidal, available true, fine 0.0 and 1 active loan (it should authorise), and with Diego Alonso, available true, fine 4.50 and 0 loans (it should reject).
Exercise 3: safe reference comparison
Given two variables String reference and String searchedReference, either of which may be null, write the logic that prints:
"MATCH"if both have the same content ignoring case."NO REFERENCE"if either of the two isnullor blank."NO MATCH"in any other case.
The program cannot stop with a NullPointerException in any possible combination. Test at least these four: ("REF-001", "ref-001"), ("REF-001", "REF-002"), (null, "REF-001"), ("REF-001", null).
Solutions
Solution 1
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
String title = "Effective Java";
boolean available = false;
int daysRemaining = 4;
// Ordered ladder: first the most specific case (available),
// then the two subcases of "not available".
if (available) {
System.out.println(title + ": AVAILABLE for immediate loan");
} else if (daysRemaining > 0) {
System.out.println(title + ": ON LOAN, back in " + daysRemaining + " days");
} else {
System.out.println(title + ": ON LOAN AND OVERDUE, chase the employee");
}
}
}Output with the example values: Effective Java: ON LOAN, back in 4 days.
Test the other two cases by changing available to true and then leaving available = false with daysRemaining = 0. Notice that you do not need to write else if (!available && daysRemaining > 0): being in the else, we already know available is false. Repeating the condition would just be noise.
Solution 2
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
final int LOAN_DAYS = 15;
final int MAX_LOANS = 3;
String employee = "Nuria Vidal";
String title = "Refactoring";
String isbn = "978-0000000003";
boolean available = true;
double outstandingFine = 0.0;
int activeLoans = 1;
// --- Guard clauses: the invalid cases are discarded first ---
if (employee == null || employee.isBlank()) {
System.err.println("REJECTED: the employee name is mandatory.");
return;
}
if (!available) {
System.err.println("REJECTED: '" + title + "' is not available.");
return;
}
// Comparing a double with a tolerance instead of != 0.0
if (outstandingFine > 0.001) {
System.err.printf("REJECTED: %s has %.2f EUR in outstanding fines.%n",
employee, outstandingFine);
return;
}
if (activeLoans >= MAX_LOANS) {
System.err.printf("REJECTED: %s already has %d active loans (maximum %d).%n",
employee, activeLoans, MAX_LOANS);
return;
}
// --- Happy path: no extra indentation, no pending conditions ---
System.out.println("=== LOAN AUTHORISED ===");
System.out.printf("%-12s %s%n", "Employee:", employee);
System.out.printf("%-12s %s (%s)%n", "Book:", title, isbn);
System.out.printf("%-12s Return in %d days%n", "Term:", LOAN_DAYS);
System.out.printf("%-12s %d of %d%n", "Loans:", activeLoans + 1, MAX_LOANS);
}
}Output with Nuria Vidal:
=== LOAN AUTHORISED === Employee: Nuria Vidal Book: Refactoring (978-0000000003) Term: Return in 15 days Loans: 2 of 3
With employee = "Diego Alonso" and outstandingFine = 4.50, the third guard fires and the program prints on System.err:
The important thing about this exercise: four conditions and zero nesting. Each guard is independent, has its own message and can be added or removed without touching the others.
Solution 3
package com.nexussoftware.bibliotech;
public class BiblioTechApp {
public static void main(String[] args) {
String reference = "REF-001";
String searchedReference = null;
// Null guard with short-circuiting: if the first part of each
// '||' is true, isBlank() is never called on null.
boolean anyMissing = reference == null || reference.isBlank()
|| searchedReference == null || searchedReference.isBlank();
if (anyMissing) {
System.out.println("NO REFERENCE");
} else if (reference.equalsIgnoreCase(searchedReference)) {
System.out.println("MATCH");
} else {
System.out.println("NO MATCH");
}
}
}Results for the four requested combinations:
reference |
searchedReference |
Output |
|---|---|---|
"REF-001" |
"ref-001" |
MATCH |
"REF-001" |
"REF-002" |
NO MATCH |
null |
"REF-001" |
NO REFERENCE |
"REF-001" |
null |
NO REFERENCE |
Keys to the solution:
- The compound condition is extracted into
anyMissing, a named boolean. It reads better and, when debugging, you can inspect its value at a glance. - The order inside each
||is critical:reference == nullcomes beforereference.isBlank(). If you swapped them, the program would blow up withnull. - By the time the
else ifis reached, both variables are guaranteed non-null, soequalsIgnoreCaseis safe. If the ladder were the other way round, it would not be.
Conclusion
Your program now decides. You have seen the simple if and why braces must never be omitted; if-else and the exhaustiveness guarantee it gives the compiler; the else if ladder, in which the order of the conditions is the logic; compound conditions with short-circuiting used as null guards; guard clauses as the antidote to nesting; the division of labour between the ternary (choosing a value) and if-else (running actions); the correct comparison of Strings with equals and equalsIgnoreCase, including the Yoda comparison for surviving null; and, above all, the habit of writing the decision table first and the code afterwards, which is what turned BiblioTech's four business rules into a clean, verifiable ladder.
BiblioTechApp now classifies the four fine cases correctly, warns when the cap is reached and rejects absurd input. But it gives up at the first hurdle: when it detects invalid data, it ends instead of asking again, and it still processes one single return per run.
In the next lesson, Loops, you will break that barrier. With while, do-while and for your program will be able to repeat a block as many times as needed: retrying the reading until the value is valid, going through the whole library catalog and accumulating the fines of an entire batch of returns without duplicating a single line of code.
Java Programming Course
Module 1: Introduction to Java
- Introduction to Java
- Setting Up the Development Environment
- Basic Syntax and Structure
- Variables and Data Types
- Operators
- Console Input and Output
- Your First Complete Program: BiblioTech
Module 2: Control Flow
- Conditional Statements
- Loops
- Switch Statements
- Break and Continue
- Debugging and Execution Traces
- Project: The BiblioTech Interactive Menu
Module 3: Object-Oriented Programming
- Introduction to OOP
- Classes and Objects
- Methods
- Constructors
- Inheritance
- Polymorphism
- Encapsulation
- Abstraction
- The Object Class: equals, hashCode and toString
Module 4: Advanced Object-Oriented Programming
- Interfaces
- Abstract Classes
- Inner Classes
- Anonymous Classes
- Lambda Expressions
- Functional Interfaces and Method References
- Enums and Records
Module 5: Data Structures and Collections
- Arrays
- The Collections Framework
- ArrayList
- LinkedList
- HashMap
- HashSet
- Queue and Deque
- Stack
- Sorting and Searching Collections
Module 6: Exception Handling
- Introduction to Exceptions
- The Try-Catch Block
- Throw and Throws
- Custom Exceptions
- The Finally Block
- Try-with-resources and AutoCloseable
- Error Handling Strategies and Logging
Module 7: File Input/Output
- Reading Files
- Writing Files
- File Streams
- BufferedReader and BufferedWriter
- Serialization
- The NIO.2 API: Path and Files
- Interchange Formats: CSV and Properties
Module 8: Multithreading and Concurrency
- Introduction to Multithreading
- Creating Threads
- Thread Lifecycle
- Synchronization
- Concurrency Utilities
- Concurrent Collections and Atomic Variables
- Asynchronous Tasks with CompletableFuture
Module 9: Networking
- Introduction to Networking
- Sockets
- ServerSocket
- DatagramSocket and DatagramPacket
- URL and HttpURLConnection
- The Modern HTTP Client
Module 10: Advanced Topics
- Generics
- Annotations
- Reflection
- Java 8 Features: Streams and Optional
- Dates and Times with java.time
- Java 9 and Beyond
- Memory, Garbage Collection and Performance
Module 11: Java Frameworks and Libraries
- Introduction to Java Frameworks
- Spring Framework
- Hibernate
- JUnit
- Maven
- Advanced Testing with Mockito
- Essential Ecosystem Libraries
