JAVA / OPERATORS
Comparing values with relational operators
Compare numbers, characters and objects with <, >, <=, >=, == and != in Java, predicting promotion, boolean results and reference-identity traps.
What you will learn
- Read every comparison as an expression that yields a boolean you can name and store
- Predict binary numeric promotion when int, char, long and double operands are mixed
- Choose equals over == for object content, and reserve == for identity or primitives
- Handle double and NaN comparisons with tolerances instead of == or a negated <
Understanding Comparing values with relational operators
Java has four ordering operators, <, >, <= and >=, plus two equality operators, == and !=. Every one of them is an expression whose value has type boolean, not the 1 or 0 that the same symbols produce in C. That has a visible consequence: you can store stock < reorder in a boolean variable and pass it around like any other value, and you cannot write if (count = 0), because an assignment to an int evaluates to an int and an int is not a condition in Java.
Before an ordering test runs, the two operands go through binary numeric promotion: the narrower type is widened until both sides share a type. Comparing an int with a double compares two doubles, and comparing a char with an int compares two ints, which is why 'a' < 'b' is true — the machine is really evaluating 97 < 98 on UTF-16 code units. The promotion is silent and can lose information, since a long compared against a double is converted to double first, so two longs that differ by 1 can test as equal once they pass 2^53.
== changes meaning the moment its operands are reference types. It compares the references, asking whether both variables point to the same object, and it never looks at fields or calls a method. Two String objects holding the same characters can therefore be !=, which is why content comparison uses equals, and why ordering objects uses compareTo — < does not compile for reference types at all.
public class RelationalDemo {
public static void main(String[] args) {
int stock = 7;
int reorder = 10;
boolean needsRestock = stock < reorder;
System.out.println("stock < reorder -> " + needsRestock);
System.out.println("stock >= 7 -> " + (stock >= 7));
System.out.println("stock != 0 -> " + (stock != 0));
double target = 7.0;
System.out.println("stock == target -> " + (stock == target));
System.out.println("'a' < 'b' -> " + ('a' < 'b'));
System.out.println("0.1 + 0.2 == 0.3 -> " + (0.1 + 0.2 == 0.3));
}
}A relational operator promotes its operands to a common type, asks one yes/no question and returns a boolean, and when the operands are references == asks about identity rather than content.
Worked examples
== on String objects
Shows that == on references answers a different question than equals, even when the characters match.
public class StringComparison {
public static void main(String[] args) {
String a = "java";
String b = "java";
String c = new String("java");
System.out.println("a == b: " + (a == b));
System.out.println("a == c: " + (a == c));
System.out.println("a.equals(c): " + a.equals(c));
System.out.println("lengths equal: " + (a.length() == c.length()));
}
}Example explained
Line 1a and b are assigned the same interned literal, so a == b is true because of how the constant is stored, not because the characters were checked.
Line 2new String("java") allocates a second object with identical characters, so a == c is false.
Line 3a.equals(c) inspects the characters, which is the comparison the code actually intended.
Line 4a.length() == c.length() compares two ints, so == is exactly the right operator on that line.
NaN is unordered
Demonstrates that all four ordering tests and == are false against NaN, so negating < is not the same as >=.
public class NanComparison {
public static void main(String[] args) {
double nan = 0.0 / 0.0;
System.out.println("nan == nan: " + (nan == nan));
System.out.println("nan != nan: " + (nan != nan));
System.out.println("nan < 1.0: " + (nan < 1.0));
System.out.println("nan >= 1.0: " + (nan >= 1.0));
System.out.println("-0.0 == 0.0: " + (-0.0 == 0.0));
}
}Example explained
Line 10.0 / 0.0 yields NaN instead of throwing, because double division follows IEEE 754 rules.
Line 2nan == nan is false: NaN is defined as unordered, so it is not equal even to itself, and != is the only operator that reports true.
Line 3nan < 1.0 and nan >= 1.0 are both false, so rewriting x >= y as !(x < y) changes the result once NaN can appear.
Line 4-0.0 == 0.0 is true because == compares numeric value, even though the two values have different bit patterns.
Compare values, not differences
Shows that testing a subtraction against zero is not equivalent to comparing the operands, because int arithmetic wraps.
public class DifferenceTrap {
public static void main(String[] args) {
int small = -2000000000;
int big = 2000000000;
System.out.println("small < big: " + (small < big));
System.out.println("small - big: " + (small - big));
System.out.println("small - big < 0: " + (small - big < 0));
}
}Example explained
Line 1small < big compares the two ints directly and gives the mathematically correct answer.
Line 2small - big is -4000000000, which does not fit in an int, so it wraps around to 294967296.
Line 3Because the wrapped difference is positive, small - big < 0 reports false while small < big reports true.
Line 4Prefer the direct comparison unless you can prove the difference cannot overflow.
Important notes
Ordering operators are not defined for boolean: true > false does not compile, although == and != do work on boolean operands.
Comparing a long against a double promotes the long to double first, so 9007199254740993L == 9007199254740992.0 is true; precision is lost before the test runs.
Common mistakes
Comparing strings with ==: it prints true for two literals because the compiler shares one interned object, then returns false for the same characters read at runtime, so the branch silently stops firing.
Chaining comparisons as in if (0 < age && age < 130 ? true : false) written instead as if (0 < age < 130): the compiler rejects it with a bad operand type error, since 0 < age is a boolean and < has no boolean form.
Using == on two Integer objects: it compares references, so it is true for 100 and false for 1000 because of the small-value cache, while >= and <= unbox and behave correctly — a bug that passes every test written with small numbers.
Try it yourself
Change, predict, then run
In a browser editor, print the results of 7 == 7.0, 7 < 7.5, 'Z' < 'a' and (0.1 + 0.2) == 0.3. Then replace the last line with Math.abs(0.1 + 0.2 - 0.3) < 1e-9 and note which result flipped.
Open the Java workspaceCheck your understanding
Given int i = 5; and double d = 5.0;, the expression i == d evaluates to true. Which explanation is correct?
- Java widens the int 5 to the double 5.0, then compares two equal doubles.
- Java narrows the double 5.0 to the int 5, then compares two equal ints.
- == ignores the operand types and compares the text each value would print.
- The int is autoboxed to an Integer and compared with a Double using equals.
Show answer
Binary numeric promotion always widens the narrower operand, so the comparison actually performed is 5.0 == 5.0. The narrowing option is tempting because it also predicts true here, but it would make 5 == 5.9 true as well, since truncating 5.9 gives 5; the real value of 5 == 5.9 is false.