Skip to main content

Ch 3.5 Logical Operators

Logical operators are used to combine boolean values to express more complex conditions. There are 4 of them: && (AND), || (OR), ! (NOT), and ^ (XOR). They play a key role in conditional statements and loops.


1. Summary of 4 Logical Operators

OperatorNameDescription
&&ANDtrue only when both conditions are true
||ORtrue when at least one condition is true
!NOTInverts true/false
^XORtrue when the two conditions differ

2. Complete Truth Tables

AND (&&)

ABA && B
truetruetrue
truefalsefalse
falsetruefalse
falsefalsefalse

OR (||)

ABA || B
truetruetrue
truefalsetrue
falsetruetrue
falsefalsefalse

NOT (!)

A!A
truefalse
falsetrue

XOR (^)

ABA ^ B
truetruefalse
truefalsetrue
falsetruetrue
falsefalsefalse
public class LogicalTruthTable {
public static void main(String[] args) {
boolean[] values = {true, false};

System.out.println("=== AND (&&) ===");
for (boolean a : values) {
for (boolean b : values) {
System.out.printf("%5b && %5b = %5b%n", a, b, a && b);
}
}

System.out.println("\n=== OR (||) ===");
for (boolean a : values) {
for (boolean b : values) {
System.out.printf("%5b || %5b = %5b%n", a, b, a || b);
}
}

System.out.println("\n=== XOR (^) ===");
for (boolean a : values) {
for (boolean b : values) {
System.out.printf("%5b ^ %5b = %5b%n", a, b, a ^ b);
}
}

System.out.println("\n=== NOT (!) ===");
for (boolean a : values) {
System.out.printf("!%5b = %5b%n", a, !a);
}
}
}

3. Basic Usage Examples

public class LogicalBasic {
public static void main(String[] args) {
int score = 75;
int attendance = 85;

// AND: pass if score >= 70 AND attendance >= 80
boolean pass = (score >= 70) && (attendance >= 80);
System.out.println("Pass: " + pass); // true

// OR: honor award if score >= 90 OR attendance >= 95
boolean honor = (score >= 90) || (attendance >= 95);
System.out.println("Honor: " + honor); // false

// NOT: fail status
System.out.println("Fail: " + !pass); // false

// XOR: exactly one condition is met
boolean highScore = score >= 90;
boolean highAttend = attendance >= 95;
System.out.println("Exactly one excellent: " + (highScore ^ highAttend)); // false (both false)

int x = 5;
// Range check: 0 < x < 10
boolean inRange = (x > 0) && (x < 10);
System.out.println(x + " is between 0 and 10: " + inRange); // true
}
}

4. Short-Circuit Evaluation

With && and ||, if the result can be determined from the left operand alone, the right operand is not evaluated at all. This is called short-circuit evaluation.

Short-Circuit Evaluation of &&

If the left side is false, the entire result must be false -> the right side is not executed.

public class ShortCircuitAnd {
public static void main(String[] args) {
int[] numbers = null;

// Dangerous code: NullPointerException if numbers is null
// if (numbers.length > 0 && numbers[0] == 1) { ... }

// Safe code: null check first -> if null, the right side is not executed
if (numbers != null && numbers.length > 0) {
System.out.println("First element: " + numbers[0]);
} else {
System.out.println("Array is null or empty."); // This line executes
}
}
}

Short-Circuit Evaluation of ||

If the left side is true, the entire result must be true -> the right side is not executed.

public class ShortCircuitOr {
public static void main(String[] args) {
String name = null;

// If null, skip the right-side name.equals()
boolean isAdmin = (name == null) || name.equals("admin");
// name == null -> true -> short-circuit skips right side
System.out.println("Admin access allowed: " + isAdmin); // true (null = allow)
}
}

Performance Optimization with Short-Circuit Evaluation

Placing conditions that are quickly evaluated first can improve performance.

public class ShortCircuitPerformance {
static int callCount = 0;

static boolean expensiveCheck() {
callCount++;
// Assume very complex computation here
System.out.println(" -> expensiveCheck() called (call #" + callCount + ")");
return true;
}

static boolean quickCheck(boolean val) {
System.out.println(" -> quickCheck(" + val + ") called");
return val;
}

public static void main(String[] args) {
callCount = 0;
System.out.println("--- Fast false first ---");
// quickCheck is false -> expensiveCheck not called
boolean r1 = quickCheck(false) && expensiveCheck();
System.out.println("Result: " + r1); // false, expensiveCheck not called

callCount = 0;
System.out.println("\n--- Slow check first ---");
// expensiveCheck called first, then quickCheck
boolean r2 = expensiveCheck() && quickCheck(false);
System.out.println("Result: " + r2); // false, expensiveCheck was called
}
}
Short-Circuit Evaluation Order Principles
  1. Null check before access: obj != null && obj.method()
  2. Cheaper condition first: place fast conditions earlier
  3. &&: put the condition most likely to be false on the left
  4. ||: put the condition most likely to be true on the left

5. Side Effects and Short-Circuit Evaluation Warning

If an expression is not executed due to short-circuit evaluation, any side effects of that expression also do not occur.

public class SideEffectWarning {
public static void main(String[] args) {
int i = 0;

// i++ may or may not execute
boolean result = (1 > 2) && (++i > 0); // 1>2 is false -> ++i not executed
System.out.println("i = " + i); // 0 (not incremented!)
System.out.println("result = " + result); // false

// With OR
int j = 0;
boolean result2 = (1 < 2) || (++j > 0); // 1<2 is true -> ++j not executed
System.out.println("j = " + j); // 0 (not incremented!)
}
}
Do not write code that relies on short-circuit side effects

Incrementing variables or changing state inside conditions may not execute as intended due to short-circuit evaluation. Handle side-effect operations separately, outside of conditionals.


6. Difference Between & and &&, | and ||

OperatorTypeShort-CircuitOperand Type
&&Logical ANDYesboolean
&Bitwise AND / Logical ANDNoInteger or boolean
||Logical ORYesboolean
|Bitwise OR / Logical ORNoInteger or boolean

When & and | are used with boolean, they perform logical operations, but both sides are always evaluated without short-circuiting.

public class SingleVsDouble {
static boolean checkA() {
System.out.println("A evaluated");
return false;
}
static boolean checkB() {
System.out.println("B evaluated");
return true;
}

public static void main(String[] args) {
System.out.println("--- && (with short-circuit) ---");
boolean r1 = checkA() && checkB(); // Only A evaluated (A=false -> B skipped)
System.out.println("Result: " + r1);

System.out.println("\n--- & (no short-circuit) ---");
boolean r2 = checkA() & checkB(); // Both A and B evaluated
System.out.println("Result: " + r2);
}
}

Output:

--- && (with short-circuit) ---
A evaluated
Result: false

--- & (no short-circuit) ---
A evaluated
B evaluated
Result: false
When to use & and | with boolean

Rarely used in practice. Only use them in special cases where both conditions must always be evaluated (e.g., both methods must log). In general, always use && and ||.


7. De Morgan's Law

De Morgan's Law is useful for simplifying complex conditionals or creating the opposite condition.

!(A && B)  ==  (!A) || (!B)
!(A || B) == (!A) && (!B)
public class DeMorgan {
public static void main(String[] args) {
int age = 25;
boolean hasTicket = false;

// Original condition: deny entry if age < 18 OR no ticket
boolean deny = (age < 18) || !hasTicket;
System.out.println("Entry denied: " + deny); // true

// Applying De Morgan's Law: allow = !(deny) = !(age<18 || !hasTicket)
// = (age >= 18) && hasTicket
boolean allow = (age >= 18) && hasTicket;
System.out.println("Entry allowed: " + allow); // false

// Verify: deny == !allow
System.out.println("Verified: " + (deny == !allow)); // true

// Practical example: inverting conditions
boolean isWeekend = true;
boolean isHoliday = false;

// Workday: not a weekend AND not a holiday
boolean isWorkday = !isWeekend && !isHoliday;
// De Morgan: !(isWeekend || isHoliday)
boolean isWorkday2 = !(isWeekend || isHoliday);
System.out.println("Workday: " + isWorkday); // false
System.out.println("Workday2: " + isWorkday2); // false (same result)
}
}

8. XOR (^) Usage

XOR returns true when the two values differ. It is used for toggles, encryption, and more.

public class XorUsage {
public static void main(String[] args) {
// Check if exactly one of two conditions is true
boolean condA = true;
boolean condB = false;
System.out.println("Exactly one true: " + (condA ^ condB)); // true

condA = true;
condB = true;
System.out.println("Exactly one true: " + (condA ^ condB)); // false (both true)

// Bit state toggle with XOR
int state = 0b1010;
int mask = 0b0110;
System.out.println("Original: " + Integer.toBinaryString(state)); // 1010
System.out.println("Toggled: " + Integer.toBinaryString(state ^ mask)); // 1100

// Swap two variables without a temporary variable
int a = 5, b = 10;
a ^= b; // a = a XOR b = 5 XOR 10 = 15 (0101 ^ 1010 = 1111)
b ^= a; // b = b XOR a = 10 XOR 15 = 5 (1010 ^ 1111 = 0101)
a ^= b; // a = a XOR b = 15 XOR 5 = 10 (1111 ^ 0101 = 1010)
System.out.println("After swap: a=" + a + ", b=" + b); // a=10, b=5
}
}

9. Guide to Writing Complex Conditionals

public class ComplexCondition {
public static void main(String[] args) {
int age = 25;
String role = "user";
boolean isPremium = true;
boolean isActive = true;

// Bad: mixing && and || without parentheses -> easy to make precedence mistakes
// boolean ok = age >= 18 && role.equals("admin") || isPremium && isActive;
// This is interpreted as: (age>=18 && role.equals("admin")) || (isPremium && isActive)

// Good: use explicit parentheses to show intent
boolean isAdminAccess = (age >= 18) && role.equals("admin");
boolean isPremiumAccess = isPremium && isActive;
boolean hasAccess = isAdminAccess || isPremiumAccess;
System.out.println("Access allowed: " + hasAccess); // true

// Separate complex conditions into named variables for readability
int score = 88;
int attendance = 90;
boolean examPassed = score >= 60;
boolean attendanceMet = attendance >= 80;
boolean extraCredit = score >= 85;

boolean finalPass = examPassed && attendanceMet;
boolean withBonus = finalPass || extraCredit;
System.out.println("Final pass: " + withBonus); // true
}
}

10. Practical Example: Login Validation

import java.util.Scanner;

public class LoginValidator {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);

System.out.print("Enter ID: ");
String id = scanner.nextLine();

System.out.print("Enter password: ");
String password = scanner.nextLine();

// ===== ID Validation =====
// Rules: 4~20 characters, only letters and digits
boolean idLengthOk = (id.length() >= 4) && (id.length() <= 20);
boolean idPatternOk = id.matches("[a-zA-Z0-9]+"); // Only letters and digits
boolean idValid = idLengthOk && idPatternOk;

// ===== Password Validation =====
// Rules: 8+ characters, must contain uppercase, digit, special character
boolean pwLengthOk = password.length() >= 8;
boolean pwHasUpper = !password.equals(password.toLowerCase()); // Contains uppercase
boolean pwHasDigit = password.matches(".*[0-9].*"); // Contains digit
boolean pwHasSpecial = password.matches(".*[!@#$%^&*].*"); // Contains special char

boolean pwValid = pwLengthOk && pwHasUpper && pwHasDigit && pwHasSpecial;

// ===== Output Results =====
System.out.println("\n===== Validation Results =====");

System.out.println("[ID]");
System.out.println(" Length (4~20): " + (idLengthOk ? "Pass" : "Fail (" + id.length() + " chars)"));
System.out.println(" Letters/digits only: " + (idPatternOk ? "Pass" : "Fail (contains special chars)"));
System.out.println(" Overall: " + (idValid ? "Valid" : "Invalid"));

System.out.println("\n[Password]");
System.out.println(" Length (8+): " + (pwLengthOk ? "Pass" : "Fail (" + password.length() + " chars)"));
System.out.println(" Contains uppercase: " + (pwHasUpper ? "Pass" : "Fail"));
System.out.println(" Contains digit: " + (pwHasDigit ? "Pass" : "Fail"));
System.out.println(" Contains special char: " + (pwHasSpecial ? "Pass" : "Fail"));
System.out.println(" Overall: " + (pwValid ? "Valid" : "Invalid"));

System.out.println("\n[Login Attempt]");
if (idValid && pwValid) {
System.out.println("Input format is correct. Sending authentication request to server...");
} else {
System.out.println("Input format is incorrect. Please check and try again.");
}

scanner.close();
}
}

Sample output (id: user1, password: Pass1!23):

===== Validation Results =====
[ID]
Length (4~20): Pass
Letters/digits only: Pass
Overall: Valid

[Password]
Length (8+): Pass
Contains uppercase: Pass
Contains digit: Pass
Contains special char: Pass
Overall: Valid

[Login Attempt]
Input format is correct. Sending authentication request to server...

11. Key Summary

OperatorFeaturesPrimary Use Case
&&AND, short-circuitNull check before access: obj != null && obj.doSomething()
||OR, short-circuitDefault value: value != null || useDefault()
!NOTInvert condition: !isEmpty(), !isError()
^XORTwo conditions differ: toggle, encryption
&, |No short-circuitSpecial cases where both conditions must always be evaluated