3.3 Comparisons, Equality, and Short-Circuit Logic
Prerequisites: Arithmetic, Division, and Remainders
Estimated time: 30 minutes
You are ready to continue when: You can distinguish between numeric comparison, reference identity, content equality, and short-circuit evaluation
Relational Operators Produce boolean
int apples = 25;
boolean enough = apples > 20;
boolean exactlyTen = apples == 10;
boolean nonzero = apples != 0;
boolean atLeastFive = apples >= 5;
boolean belowThirty = apples < 30;<, <=, >, and >= are applicable to numeric types. Some operations will first perform numeric promotion. boolean cannot use relational operators.
=, ==, and Content Equality
= is an assignment operator, == is an equality operator. You can't simply say "the compiler will protect you":
boolean expected = false;
boolean actual = true;
if (expected = actual) {
System.out.println("runs");
}This can compile because the type and value of the assignment expression expected = actual are both boolean. If the intent was a comparison, the result is a defect. Reduce assignments in conditions, and pair with compiler warnings, static analysis, and code reviews.
For primitive types, == compares the values after necessary conversions:
System.out.println(1 == 1L); // true
System.out.println(Double.NaN == Double.NaN); // false
System.out.println(0.0 == -0.0); // trueFor reference types, == determines if two references point to the same object or both are null, without comparing the object's business content:
String left = new String("apple");
String right = new String("apple");
System.out.println(left == right); // false
System.out.println(left.equals(right)); // trueString literals with the same content are interned, so == can return true; strings constructed at runtime need not share an object. Use the content-comparison contract when content is what matters. For a null-safe comparison:
import java.util.Objects;
boolean same = Objects.equals(left, right);Whether and how an object implements .equals() is determined by its type contract, which will be explored in depth in the subsequent object-oriented programming chapter.
Logical Operators
boolean member = true;
double spent = 150.0;
boolean discount = member && spent >= 100.0;
boolean needsReview = !member || spent > 1_000.0;| Operator | Result Rule | Can Right Side Be Skipped |
|---|---|---|
&& | Both sides are true | Skip when left is false |
|| | At least one side is true | Skip when left is true |
! | Invert boolean value | Only one operand |
& | Evaluate both sides as booleans and perform AND | Do not skip |
| | Evaluate both sides as booleans and perform OR | Do not skip |
& and | can also perform integer bitwise operations; this chapter focuses only on their boolean usage.
Short-Circuiting Is a Language Rule
int count = 5;
boolean result = count > 10 && ++count > 0;
System.out.println(result); // false
System.out.println(count); // 5The left side has already determined the result of && as false, and the language rules specify that the right side is not evaluated. This is not a compiler optimization, nor is it a coincidental behavior.
A null guard uses this guarantee:
boolean nonempty = text != null && !text.isEmpty();The order cannot be arbitrarily swapped, !text.isEmpty() && text != null will first dereference text. If the right side contains operations that must be executed, such as writes, counting, or network calls, do not rely on the logical expression to hide the flow. Use clear control statements instead.
When Floating-Point == Fits
== is for precisely comparing two floating-point values. It is suitable for scenarios where you indeed need to check for the same IEEE 754 value, but it is not appropriate for treating approximate computation results as exact decimal answers:
double actual = 0.1 + 0.2;
System.out.println(actual == 0.3); // falseThe tolerance must come from the problem's scale. A simplified example that considers both absolute and relative error:
static boolean nearlyEqual(double left, double right,
double absoluteTolerance,
double relativeTolerance) {
if (!Double.isFinite(absoluteTolerance) || absoluteTolerance < 0.0
|| !Double.isFinite(relativeTolerance) || relativeTolerance < 0.0
|| relativeTolerance >= 1.0) {
throw new IllegalArgumentException("invalid tolerance");
}
if (Double.isNaN(left) || Double.isNaN(right)) {
return false;
}
if (left == right) {
return true;
}
if (!Double.isFinite(left) || !Double.isFinite(right)) {
return false;
}
double difference = Math.abs(left - right);
double scale = Math.max(Math.abs(left), Math.abs(right));
return difference <= Math.max(absoluteTolerance,
relativeTolerance * scale);
}This simplified version requires a finite, nonnegative absolute tolerance and a relative tolerance in [0, 1); it rejects invalid tolerances. It is still not a universal comparator. Testing frameworks often provide assertions with tolerance; for precise decimal rules such as with currency, BigDecimal should be used along with explicit scale and rounding.
Double.compare and Double.equals also define different handling of NaN and positive and negative zeros for scenarios such as sorting and sets, which differ from ==. Before choosing, examine the API contract.
De Morgan's Laws Help Simplify Negation
!(A && B) is equivalent to !A || !B
!(A || B) is equivalent to !A && !BFor example:
boolean outside = !(age >= 18 && age <= 65);
boolean sameMeaning = age < 18 || age > 65;The latter is often more straightforward. Rewrite it and validate with boundary tests for 17, 18, 65, and 66, don't rely solely on the appearance of the formula.
Professional Reference
| Requirement | Approach |
|---|---|
| Equality of primitive values | ==, note value promotion and floating-point special values |
| Object identity | == |
| String content | left.equals(right) or Objects.equals |
| Null-safe access | value != null && ... |
| Both sides must execute | Use clear statements; Boolean &/|, although not short-circuiting, should not hide side effects |
| Approximate floating-point comparison | Defined by domain as absolute/relative tolerance |
| Exact decimal equality | Use appropriate decimal types and explicit contracts |
Before Moving On
Remember that if (expected = actual) may compile successfully, == compares identity for references, and &&'s short-circuiting is a language guarantee. A floating-point tolerance needs a reason grounded in the problem’s scale.
The next page will place these boundaries in the same set of experiments.
→ Next: Expression and Operator Experiment
Specification: JLS §15.21: Equality Operator, §15.23–15.24: Conditional AND, Conditional OR.