☕ 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
}Related Topics in Java Basics
Variables in Java
A variable is just a named box in memory that holds a value. Java is strict about what goes in each box — you tell it the type upfront. Once you get this, the rest of Java clicks into place.
Data Types in Java
Java needs to know exactly what kind of data it's dealing with before it can store or process it. Integers, decimals, characters, true/false — each has its own type. Knowing which to use (and why) makes your programs efficient and bug-free.
Primitive Data Types
Java has eight primitive data types — the most basic building blocks for storing data. Unlike objects, primitives are stored directly in memory, making them fast and efficient. Understanding each type, its size, range, and when to use it is fundamental to writing correct Java programs.
Non-Primitive Data Types
Non-primitive data types — also called reference types — are everything beyond Java's eight primitives. Strings, arrays, classes, interfaces, enums, and records all fall into this category. They're more powerful than primitives, but they work differently in memory, comparison, and nullability. Understanding the distinction is essential for writing correct Java.