☕ Java

Logical Operators

Logical operators combine boolean expressions into more complex conditions. Java has six of them — short-circuit and non-short-circuit variants of AND, OR, and XOR, plus logical NOT. Understanding short-circuit evaluation is essential for writing both safe and efficient conditions.

The Six Logical Operators

Java has six logical operators that work with boolean values:
Java
// Short-circuit versions (most common):
&&    // logical AND — true if BOTH operands are true
||    // logical OR  — true if AT LEAST ONE operand is true
!     // logical NOT — inverts boolean value (unary)

// Non-short-circuit versions (evaluate both sides always):
&     // bitwise/logical AND — evaluates both sides
|     // bitwise/logical OR  — evaluates both sides
^     // logical XOR — true if operands are DIFFERENT

&& — Logical AND (Short-Circuit)

&& returns true only if both operands are true. It short-circuits: if the left operand is false, the right operand is never evaluated because the result is already determined to be false.
Java
// Truth table for &&:
System.out.println(true  && true);   // true
System.out.println(true  && false);  // false
System.out.println(false && true);   // false
System.out.println(false && false);  // false

// Short-circuit — right side skipped if left is false:
int x = 0;
boolean result = (x != 0) && (10 / x > 1);  // safe — division never happens
// If (x != 0) is false, (10 / x > 1) is never evaluated

// Most important use — null safety:
String name = null;
if (name != null && name.length() > 0) {
    System.out.println("Name: " + name);
}
// Without short-circuit, name.length() would throw NullPointerException

// Chained conditions — all must be true:
if (user != null
        && user.isActive()
        && user.hasRole("ADMIN")
        && user.getAge() >= 18) {
    grantAccess(user);
}
// Each condition checked only if all previous are true

|| — Logical OR (Short-Circuit)

|| returns true if at least one operand is true. It short-circuits: if the left operand is true, the right operand is never evaluated.
Java
// Truth table for ||:
System.out.println(true  || true);   // true
System.out.println(true  || false);  // true
System.out.println(false || true);   // true
System.out.println(false || false);  // false

// Short-circuit — right side skipped if left is true:
boolean result = checkCache() || loadFromDatabase();
// If checkCache() returns true, loadFromDatabase() is never called

// Default value pattern:
String displayName = username != null ? username : "Guest";
// Or with method calls:
String config = System.getenv("APP_NAME") != null
    ? System.getenv("APP_NAME")
    : "DefaultApp";

// Chained fallbacks:
String value = getPrimary() != null ? getPrimary()
             : getSecondary() != null ? getSecondary()
             : getDefault();

// Validation — any condition being true means invalid:
if (name == null || name.isEmpty() || name.length() > 50) {
    throw new IllegalArgumentException("Invalid name");
}

! — Logical NOT

! is a unary operator that inverts a boolean value. It's placed before its operand and has very high precedence.
Java
// Basic NOT:
System.out.println(!true);    // false
System.out.println(!false);   // true

// NOT with expressions:
int x = 5;
System.out.println(!(x > 3));    // false — x > 3 is true, inverted = false
System.out.println(!(x == 10));  // true  — x == 10 is false, inverted = true

// NOT in conditions:
boolean isLoggedIn = false;
if (!isLoggedIn) {
    redirectToLogin();
}

// Double negation — avoid in practice:
boolean isActive = true;
boolean isNotInactive = !!isActive;   // true — same as isActive

// De Morgan's laws — useful for simplifying conditions:
// !(A && B) is equivalent to (!A || !B)
// !(A || B) is equivalent to (!A && !B)

// Example — these are equivalent:
if (!(age < 18 || score < 50)) { }         // hard to read
if (age >= 18 && score >= 50) { }           // cleaner — De Morgan applied

// NOT with method returns:
List<String> list = new ArrayList<>();
if (!list.isEmpty()) {
    System.out.println("Has items: " + list.size());
}

^ — Logical XOR

^ (exclusive OR) returns true if the operands are different — one true and one false. Returns false if both are the same. XOR doesn't short-circuit — both sides are always evaluated.
Java
// Truth table for ^ (XOR):
System.out.println(true  ^ true);   // false — same
System.out.println(true  ^ false);  // true  — different
System.out.println(false ^ true);   // true  — different
System.out.println(false ^ false);  // false — same

// XOR use cases:
// Exactly one of two conditions must be true:
boolean hasCard = true;
boolean hasCash = false;
if (hasCard ^ hasCash) {
    System.out.println("Pay with one method");  // exactly one payment method
}

// Toggle a boolean:
boolean flag = true;
flag = flag ^ true;   // false — toggles
flag = flag ^ true;   // true  — toggles back

// Or more simply:
flag = !flag;

// Checking mutual exclusivity:
boolean isAdmin = true;
boolean isGuest = false;
if (isAdmin ^ isGuest) {
    // user is either admin OR guest, but not both and not neither
}

& and | — Non-Short-Circuit Versions

& and | work like && and || for booleans but always evaluate both operands. Use these only when both sides must execute regardless of the result — usually when side effects are required.
Java
// & — both sides always evaluated:
boolean a = false;
boolean b = sideEffectMethod();  // always called, even though a is false
boolean result = a & b;

// | — both sides always evaluated:
boolean c = true;
boolean d = anotherSideEffect();  // always called, even though c is true
boolean result2 = c | d;

// When to use & and | over && and ||:
// Rarely — only when you need guaranteed side effects on both sides

// Example — two validators that both log independently:
boolean valid = validateName(name) & validateEmail(email);
// Both methods run and log, even if the first one fails
// With &&, second validator would be skipped if first returns false

// For booleans, & and | produce the same logical result as && and ||:
System.out.println(true & false);   // false — same as &&
System.out.println(true | false);   // true  — same as ||

// WARNING — & and | on integers are BITWISE, not logical:
int x = 5, y = 3;
System.out.println(x & y);   // 1  — bitwise AND on bits
System.out.println(x | y);   // 7  — bitwise OR on bits
// This is completely different from boolean & and |

Short-Circuit Evaluation — Practical Patterns

Short-circuit evaluation is one of Java's most practically useful features. These patterns show up constantly in real codebases:
Java
// ── Pattern 1: Null guard ────────────────────────────────────────
if (user != null && user.getProfile() != null && user.getProfile().getEmail() != null) {
    sendEmail(user.getProfile().getEmail());
}

// ── Pattern 2: Cache-first loading ───────────────────────────────
String value = cache.get(key) != null ? cache.get(key) : loadAndCache(key);
// Or:
if (cache.containsKey(key) || loadIntoCache(key)) {
    return cache.get(key);
}

// ── Pattern 3: Validation chain ──────────────────────────────────
boolean isValid = isNotNull(input)
    && isNotEmpty(input)
    && matchesPattern(input)
    && isUnique(input);   // each check only runs if all previous pass

// ── Pattern 4: Short-circuit assignment idiom ─────────────────────
String name = getNameFromCache()
    != null ? getNameFromCache() : fetchNameFromDB();

// ── Pattern 5: Conditional execution ─────────────────────────────
boolean debug = false;
debug && System.out.println("Debug info");  // won't compile — println is void
// Instead:
if (debug) System.out.println("Debug info");

// ── Pattern 6: Guard with OR ──────────────────────────────────────
void process(String input) {
    if (input == null || input.isEmpty()) return;  // early exit
    // safe to use input here
}