Complex Conditionals
Compound conditions, De Morgan's Laws, and the guard pattern
After this lesson, you will be able to:
- Build compound conditions with
&&(AND),||(OR), and!(NOT) - Apply De Morgan’s Laws to simplify negated conditions
- Use the short-circuit guard pattern to prevent crashes
- Write range checks and input validation using compound conditions
- Use the ternary operator for simple conditional assignments
When One Condition Is Not Enough
Lesson 1.3 covered if/else with a single condition. But real decisions are rarely that simple:
- “Can they rent a car?” → must be at least 25 and have a license
- “Do they get the day off?” → it is a weekend or a holiday
- “Is the score valid?” → must be at least 0 and at most 100
Each of these requires compound conditions — multiple boolean expressions combined with logical operators.
From CSCD 110: You used
and,or, andnotin Python conditionals. Java uses symbols instead:&&forand,||foror,!fornot. The logic is identical — only the syntax changes.
| Logic | Python | Java |
|---|---|---|
| Both true | age >= 21 and has_id |
age >= 21 && hasId |
| At least one true | is_weekend or is_holiday |
isWeekend \|\| isHoliday |
| Negate | not raining |
!raining |
&& (AND): Both Must Be True
&& is a checklist — every item must be checked off:
int age = 25;
boolean hasLicense = true;
if (age >= 25 && hasLicense) {
System.out.println("Can rent a car");
}
If either side is false, the whole expression is false.
|| (OR): At Least One Must Be True
|| is alternatives — having either one (or both) is enough:
boolean isWeekend = false;
boolean isHoliday = true;
if (isWeekend || isHoliday) {
System.out.println("No work today!");
}
The expression is false only when both sides are false.
! (NOT): Invert
! flips true to false and vice versa:
boolean isRaining = false;
if (!isRaining) {
System.out.println("Go outside!");
}
Precedence: ! Before && Before ||
Logical operators have a precedence order, just like arithmetic:
| Priority | Operator | Meaning |
|---|---|---|
| 1 (highest) | ! |
NOT |
| 2 | && |
AND |
| 3 (lowest) | || |
OR |
This means && binds tighter than ||:
// Java sees this as: a || (b && c)
a || b && c
// If you wanted (a || b) && c, you MUST use parentheses
Common Pitfall: Don’t rely on precedence. Always use parentheses to make compound conditions readable. If you have to think about precedence, your reader will too.
Given boolean a = true, b = false, c = true;, what is a || b && c?
The Range-Checking Pattern
Checking whether a value falls within a range is one of the most common compound conditions:
// In range: use &&
if (score >= 0 && score <= 100) {
System.out.println("Valid score");
}
// Out of range: use ||
if (score < 0 || score > 100) {
System.out.println("Invalid: must be 0-100");
}
Common Pitfall: Java does not support chained comparisons like math notation or Python:
// WRONG — does not compile in Java if (0 <= score <= 100) { } // CORRECT — two comparisons joined with && if (score >= 0 && score <= 100) { }
Short-Circuit Evaluation: The Guard Pattern
Java’s && and || are short-circuit operators — they stop evaluating as soon as the result is determined:
false && ...→ result isfalse(right side skipped)true || ...→ result istrue(right side skipped)
This is not just an optimization — it is a safety feature:
// Safe: checks for zero BEFORE dividing
if (count != 0 && total / count > 10) {
System.out.println("High average");
}
// If count is 0, Java stops at "count != 0" (false)
// and never evaluates "total / count" (which would crash)
// Safe: checks for null BEFORE calling .length()
if (name != null && name.length() > 0) {
System.out.println("Name: " + name);
}
The Trick: The guard pattern — put the safety check first in an
&&expression. If the guard fails, the dangerous operation is never attempted.
De Morgan’s Laws
De Morgan’s Laws tell you how to distribute negation across && and ||:
| Law | Equivalence |
|---|---|
| Law 1 | !(A && B) = !A \|\| !B |
| Law 2 | !(A \|\| B) = !A && !B |
The recipe: flip && to || (or vice versa), and negate each piece.
// Original: eligible if 18+ AND has license
if (age >= 18 && hasLicense) {
System.out.println("Eligible");
}
// De Morgan's: NOT eligible means under 18 OR no license
if (age < 18 || !hasLicense) {
System.out.println("Not eligible");
}
A practical application — simplifying confusing negated conditions:
// Confusing: outer ! with compound condition
if (!(temperature >= 68 && temperature <= 72 && humidity < 60)) {
System.out.println("Uncomfortable");
}
// Simplified with De Morgan's — much easier to read
if (temperature < 68 || temperature > 72 || humidity >= 60) {
System.out.println("Uncomfortable");
}
Key Insight: In-range checks use
&&. Out-of-range checks use||. These are De Morgan duals of each other — if you know one, flip the operators and negate each piece to get the other.
Which expression is equivalent to !(x > 5 && y < 10)?
The Ternary Operator
For simple conditional assignments, the ternary operator is a one-line if-else:
condition ? valueIfTrue : valueIfFalse
int score = 85;
String status = (score >= 60) ? "Pass" : "Fail";
System.out.println(status); // "Pass"
// Equivalent if-else:
String status;
if (score >= 60) {
status = "Pass";
} else {
status = "Fail";
}
Good uses: simple assignments, choosing labels, inline expressions:
int max = (a > b) ? a : b;
String label = (count == 1) ? "item" : "items";
System.out.println(count + " " + label);
Key Insight: Use the ternary for simple, one-value assignments. If the condition or values are complex, or you need to do more than assign a value, use if-else. Never nest ternary operators — it becomes unreadable.
Input Validation: Validate Before You Use
Every interactive program should follow: Input → Validate → Process → Output. Validate before you use the data:
import java.util.Scanner;
public class GradeCalculator {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Enter score (0-100): ");
int score = scanner.nextInt();
// Validate BEFORE using
if (score < 0 || score > 100) {
System.out.println("Error: Score must be between 0 and 100.");
return;
}
// Safe to process — score is guaranteed 0-100
String grade;
if (score >= 90) {
grade = "A";
} else if (score >= 80) {
grade = "B";
} else if (score >= 70) {
grade = "C";
} else if (score >= 60) {
grade = "D";
} else {
grade = "F";
}
System.out.println("Score: " + score + " → Grade: " + grade);
}
}
Common Pitfall: Validate before using data, not after. If you divide before checking for zero, the crash happens before your error message.
Complete Example: Discount Calculator
This program combines compound conditions, input validation, the ternary operator, and printf:
import java.util.Scanner;
public class DiscountCalculator {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
System.out.print("Loyalty member? (true/false): ");
boolean isMember = scanner.nextBoolean();
System.out.print("Is it a holiday? (true/false): ");
boolean isHoliday = scanner.nextBoolean();
System.out.print("Enter total: $");
double total = scanner.nextDouble();
if (total < 0) {
System.out.println("Error: Total cannot be negative.");
return;
}
// Gets discount if: member, OR (holiday AND total > 50)
boolean getsDiscount = isMember || (isHoliday && total > 50);
String status = getsDiscount ? "DISCOUNT APPLIED" : "No discount";
double discount = getsDiscount ? total * 0.10 : 0;
double finalTotal = total - discount;
System.out.println("\n--- Receipt ---");
System.out.printf("Subtotal: $%.2f%n", total);
System.out.printf("Discount: $%.2f (%s)%n", discount, status);
System.out.printf("Total: $%.2f%n", finalTotal);
}
}
Sample run:
Loyalty member? (true/false): false
Is it a holiday? (true/false): true
Enter total: $75.00
--- Receipt ---
Subtotal: $75.00
Discount: $7.50 (DISCOUNT APPLIED)
Total: $67.50
Why is count != 0 && total / count > 10 safe from division by zero?
Summary
Compound conditions let you express complex decision logic. && requires both sides true (checklist). || requires at least one side true (alternatives). ! inverts.
Precedence: ! before && before ||. Always use parentheses for clarity.
Short-circuit evaluation is a safety tool: guard && dangerousOperation prevents crashes when the guard fails.
De Morgan’s Laws simplify negated conditions: flip && to || and negate each piece. In-range checks use &&; out-of-range checks use ||.
The ternary operator (condition ? a : b) is a concise one-line if-else for simple assignments. Validate input before processing — the pattern is Input → Validate → Process → Output.
Next lesson: Loop patterns and debugging — fencepost problems, sentinel loops, and boolean flag patterns that build on everything you have learned so far.