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
| Operator | Name | Description |
|---|---|---|
&& | AND | true only when both conditions are true |
|| | OR | true when at least one condition is true |
! | NOT | Inverts true/false |
^ | XOR | true when the two conditions differ |
2. Complete Truth Tables
AND (&&)
| A | B | A && B |
|---|---|---|
| true | true | true |
| true | false | false |
| false | true | false |
| false | false | false |
OR (||)
| A | B | A || B |
|---|---|---|
| true | true | true |
| true | false | true |
| false | true | true |
| false | false | false |
NOT (!)
| A | !A |
|---|---|
| true | false |
| false | true |
XOR (^)
| A | B | A ^ B |
|---|---|---|
| true | true | false |
| true | false | true |
| false | true | true |
| false | false | false |
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
}
}
- Null check before access:
obj != null && obj.method() - Cheaper condition first: place fast conditions earlier
&&: put the condition most likely to be false on the left||: 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!)
}
}
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 ||
| Operator | Type | Short-Circuit | Operand Type |
|---|---|---|---|
&& | Logical AND | Yes | boolean |
& | Bitwise AND / Logical AND | No | Integer or boolean |
|| | Logical OR | Yes | boolean |
| | Bitwise OR / Logical OR | No | Integer 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
& and | with booleanRarely 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
| Operator | Features | Primary Use Case |
|---|---|---|
&& | AND, short-circuit | Null check before access: obj != null && obj.doSomething() |
|| | OR, short-circuit | Default value: value != null || useDefault() |
! | NOT | Invert condition: !isEmpty(), !isError() |
^ | XOR | Two conditions differ: toggle, encryption |
&, | | No short-circuit | Special cases where both conditions must always be evaluated |