Topic 1.8
Comparison and Logical Operators
In one line
Comparison operators (==, !=, <, >, <=, >=) ask a yes/no question and produce a boolean; logical operators (&&, ||, !) combine those answers. && and || short-circuit, skipping the right side when the left already decides the result, which is both an optimisation and a safety tool.
Think of it like this
The rule at a theme-park ride is 'at least 120 cm tall AND has a ticket'. The attendant measures your height first. If you're too short, she doesn't bother asking for a ticket, because the answer is already no. That's short-circuit evaluation: tallEnough && hasTicket never checks the ticket when tallEnough is false.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Comparison operator
- An operator that compares two values and answers
trueorfalse, like<or==. - Logical operator
- An operator that combines booleans:
&&(and),||(or),!(not). - Condition
- A boolean expression used to decide something, like
age >= 18. - Short-circuit
- Skipping the right side of
&&or||when the left side already decides the answer. - Reference
- The 'address' Java uses to reach an object. Two variables can hold references to the same object or to two different ones.
- Guard
- A check placed first in a condition to protect the checks after it, like
x != null &&. - Truth table
- A small table listing the result of a logical operator for every combination of inputs.
- De Morgan's laws
- Two rules for negating combined conditions: 'not (A and B)' is 'not A or not B', and 'not (A or B)' is 'not A and not B'.
Step by step
01Every comparison is a question with a boolean answer
age >= 18 is an expression of type boolean, just like age + 1 is an expression of type int. You can store it (boolean adult = age >= 18;), print it, return it, or use it in an if.
Don't write if (adult == true); adult is already a boolean. Likewise, instead of if (x > 5) return true; else return false; write return x > 5;.
02== on primitives vs objects
For int, double, char and the other primitives, == compares the values: 3 == 3 is true.
For objects, the variables hold references, and == compares the references. Two different String objects with the same letters are not ==. That's why you must use a.equals(b) for Strings; Topic 6.2 shows when == happens to work and why you mustn't rely on it.
The same trap hides in wrapper types: Integer values from -128 to 127 are cached and shared, so == happens to work for them, but Integer 128 objects are usually distinct. Use .equals() or compare ints.
03Truth tables for && || ^ !
&&: true only for true && true. ||: false only for false || false. ^: true when the two sides differ. !: flips.
Read conditions out loud: isMember || total > 1000 is 'members, or anyone spending over 1000'. If the English sounds wrong, the code probably is.
04Short-circuit evaluation
For left && right, Java evaluates left first. If it's false, the result must be false, so right is never evaluated. For left || right, if left is true, right is skipped.
This is a guarantee of the language, not an optimisation the compiler may or may not do, so you can rely on it: if (list != null && !list.isEmpty()), if (i < arr.length && arr[i] > 0), if (count != 0 && total / count > 10).
Order matters: put the cheap or protective check first. name.length() > 3 && name != null crashes with a NullPointerException when name is null, because the protective check comes too late.
05Non-short-circuit & and |
& and | on booleans compute the same results as && and || but always evaluate both sides. That's occasionally useful when the right side has a side effect that must always happen, but it surprises readers, so prefer two separate statements.
On integers, &, | and ^ are bitwise operators (Topic 1.9). The same symbols mean different things depending on the operand types.
06Precedence: when to add brackets
&& binds tighter than ||, like * binds tighter than +. isAdmin || isOwner && isActive means isAdmin || (isOwner && isActive), so an inactive admin passes. If you meant (isAdmin || isOwner) && isActive, you need the brackets.
Comparisons bind tighter than && and ||, so x > 0 && x < 10 needs no brackets. But == binds tighter than bitwise &, which bites in flags & MASK != 0 (Topic 1.9). When in doubt, add brackets; they cost nothing.
07Negating conditions with De Morgan
To stop a loop when done or failed, you loop while !(done || failed), which De Morgan rewrites as !done && !failed. To reject input outside 1..10, the opposite of x >= 1 && x <= 10 is x < 1 || x > 10.
A common bug is negating only half: the opposite of x >= 1 && x <= 10 is not x < 1 && x > 10 (which is never true).
Try it yourself
- 1
Flip the order
In the short-circuit example, swap the operands of the first
&&tocheck("B", true) && check("A", false). Predict which lines print. (Both checks run, because the left side istrue.) - 2
Write a leap-year test
A year is a leap year if it's divisible by 4 and not by 100, or divisible by 400. Write it as one boolean expression and test 1900 (false), 2000 (true), 2024 (true), 2026 (false).
One answer:
(y % 4 == 0 && y % 100 != 0) || y % 400 == 0. - 3
Apply De Morgan
Rewrite
!(age < 13 || age > 19)without the outer!. Check your answer gives the same result for ages 12, 13, 19 and 20.
Code & diagrams
Expected output
age > 12: true
age >= 18: false
age == 15: true
age != 15: false
10 == 10.0: true
'a' == 97: true
'a' < 'b': true
NaN < 1 || NaN >= 1: false
adult: falseExpected output
&& with false first:
checking A
result false
|| with true first:
checking A
result true
& always evaluates both:
checking A
checking B
result false
no long name, and no crashThe -128 to 127 cache is the default; it can be raised with -XX:AutoBoxCacheMax, which is one more reason never to rely on == for Integer.
Expected output
a || b && c = true
(a || b) && c = false
De Morgan holds: true
true ^ true = false
in range: true, out of range: false
Integer 127 == 127: true
Integer 128 == 128: false
128 equals 128: trueBreak it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Use = instead of == in a condition
Write if (x = 5) meaning 'if x equals 5'.
Break #2
Chain a comparison like in maths
Write if (0 < x < 10).
Break #3
Put the null check second
Write if (name.length() > 3 && name != null) with name = null.
Myth vs fact
Myth
== checks whether two Strings have the same text.
Fact
For objects, == checks whether both references point to the same object. Use .equals() for content.
Myth
& and && are the same for booleans.
Fact
They give the same result, but && skips the right side when the left is false; & always evaluates both.
Myth
The compiler might evaluate the right side of && first for speed.
Fact
Left-to-right short-circuit evaluation is guaranteed by the Java Language Specification.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
&&and||compile to conditional jumps (ifeq,ifne), not to boolean operations, so there's no 'and' instruction involved.&on booleans compiles toiandon 0/1 ints, which is why it evaluates both sides. - ▸
Floating-point comparisons follow IEEE 754: every comparison involving
NaNis false except!=, which is true. So!(x < y)is not the same asx >= ywhen NaN is possible;Double.comparegives a total order (NaN greater than everything,-0.0less than0.0). - ▸
The
Integercache range (-128 to 127) is required by JLS §5.1.7 for boxing; the upper bound can be raised with-XX:AutoBoxCacheMaxorjava.lang.Integer.IntegerCache.high, so==on boxed values can behave differently between JVM configurations. - ▸
Helpful NullPointerException messages (JEP 358) arrived in Java 14 and became the default in Java 15. They name the null expression; local variable names appear only if the class was compiled with
-g, otherwise you see<local1>.
Remember this
- 1
Relational operators
<,>,<=,>=compare numbers (includingchars, which are numbers). Equality operators==and!=work on every type. All of them produce aboolean. Mixed numeric types are promoted first, so10 == 10.0istrueand'a' == 97istrue. - 2
For primitives,
==compares values. For objects,==compares references: it asks 'are these the very same object?', not 'do they hold the same data?'. Use.equals()to compare Strings and other objects by content (Topic 6.2 and Topic 4.8). - 3
**
&&(and) is true only if both sides are true;||(or) is true if at least one side is;!(not)** flips a boolean.&&and||evaluate the left side first and skip the right side if the result is already known. This lets you write guards likes != null && s.length() > 0, where the second check would crash without the first. - 4
&,|and^also work on booleans but always evaluate both sides.^(exclusive or) is true when exactly one side is true. Use them only when you deliberately need both sides to run. - 5
Precedence (high to low):
!, then< > <= >=, then== !=, then&,^,|, then&&, then||. Soa || b && cmeansa || (b && c). Comparisons can't be chained:0 < x < 10doesn't compile; writex > 0 && x < 10. - 6
De Morgan's laws let you flip conditions safely:
!(a && b)equals!a || !b, and!(a || b)equals!a && !b. They help turn 'keep looping while not done' into clear code, and they show up in every interview about conditions.
Explain it without notes
What does short-circuit evaluation mean, and give a real example where it prevents a crash.
Why does == work for int but not reliably for String or Integer?
What is the value of true || false && false, and why?
Negate x >= 1 && x <= 10 correctly and explain the rule you used.
Practice
Print whether each of the years 1900, 2000, 2024 and 2026 is a leap year.
A shop gives free delivery to members, or to anyone whose order is at least 500, but never to blocked customers. Write the condition and test three customers.
Given String s = null, print "empty" if s is null or has length 0, and "has text" otherwise, without crashing. Then test with "hi".
Trade-offs
- ↔
Short-circuit operators make guards safe and skip needless work, but a right-hand side with side effects (like
i++) may silently not run; keep side effects out of conditions. - ↔
Long single-line conditions are compact but hard to read; naming parts (
boolean eligible = ...; boolean blocked = ...;) costs a few lines and makes logic reviewable.
Done when you can
I can use all six comparison operators and know they produce booleans.
I know
==compares values for primitives and references for objects.I can predict when
&&and||skip their right side and use guards correctly.I know operator precedence for
!, comparisons,&&and||, and add brackets when unclear.I can negate a compound condition with De Morgan's laws.