Skip to content

VOL 01 / CH 03 / LESSON 03

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 ​

java
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":

java
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:

java
System.out.println(1 == 1L); // true
System.out.println(Double.NaN == Double.NaN); // false
System.out.println(0.0 == -0.0); // true

For reference types, == determines if two references point to the same object or both are null, without comparing the object's business content:

java
String left = new String("apple");
String right = new String("apple");

System.out.println(left == right);      // false
System.out.println(left.equals(right)); // true

String 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:

java
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 ​

java
boolean member = true;
double spent = 150.0;

boolean discount = member && spent >= 100.0;
boolean needsReview = !member || spent > 1_000.0;
OperatorResult RuleCan Right Side Be Skipped
&&Both sides are trueSkip when left is false
||At least one side is trueSkip when left is true
!Invert boolean valueOnly one operand
&Evaluate both sides as booleans and perform ANDDo not skip
|Evaluate both sides as booleans and perform ORDo not skip

& and | can also perform integer bitwise operations; this chapter focuses only on their boolean usage.

Short-Circuiting Is a Language Rule ​

java
int count = 5;
boolean result = count > 10 && ++count > 0;

System.out.println(result); // false
System.out.println(count);  // 5

The 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:

java
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:

java
double actual = 0.1 + 0.2;
System.out.println(actual == 0.3); // false

The tolerance must come from the problem's scale. A simplified example that considers both absolute and relative error:

java
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 ​

text
!(A && B)  is equivalent to  !A || !B
!(A || B)  is equivalent to  !A && !B

For example:

java
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 ​

RequirementApproach
Equality of primitive values==, note value promotion and floating-point special values
Object identity==
String contentleft.equals(right) or Objects.equals
Null-safe accessvalue != null && ...
Both sides must executeUse clear statements; Boolean &/|, although not short-circuiting, should not hide side effects
Approximate floating-point comparisonDefined by domain as absolute/relative tolerance
Exact decimal equalityUse 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.

Built with VitePress | Software Systems Atlas