☕ Java

Relational Operators

Relational operators compare two values and always produce a boolean result — true or false. They're the foundation of every conditional and loop in Java. But comparing objects with == versus .equals(), and the subtleties of floating-point comparison, are where real bugs hide.

The Six Relational Operators

Java has six relational operators. All of them produce a boolean result:
Java
int a = 10, b = 20;

System.out.println(a == b);   // false — equal to
System.out.println(a != b);   // true  — not equal to
System.out.println(a >  b);   // false — greater than
System.out.println(a <  b);   // true  — less than
System.out.println(a >= b);   // false — greater than or equal to
System.out.println(a <= b);   // true  — less than or equal to

// Results are boolean — can be stored and used:
boolean isAdult = age >= 18;
boolean isValid = score >= 0 && score <= 100;
boolean isEqual = x == y;

== with Primitives vs Objects

== behaves fundamentally differently for primitives and reference types. This is the single most common source of comparison bugs in Java.
Java
// == with primitives — compares VALUES:
int x = 100, y = 100;
System.out.println(x == y);   // true — same value

double d1 = 3.14, d2 = 3.14;
System.out.println(d1 == d2); // true — same value

char c1 = 'A', c2 = 65;
System.out.println(c1 == c2); // true'A' is 65

// == with objects — compares REFERENCES (memory addresses):
String s1 = new String("Hello");
String s2 = new String("Hello");
System.out.println(s1 == s2);       // false — different objects in memory
System.out.println(s1.equals(s2));  // true  — same character sequence

// String pool — literals share the same object:
String a = "Hello";
String b = "Hello";
System.out.println(a == b);         // true  — same pooled object (accident!)
System.out.println(a.equals(b));    // true  — correct way to compare

// RULE: always use .equals() for object comparison, never ==
// The only exception: checking if a reference is null (must use ==):
if (name == null) { }        // correct — null check must use ==
if (name.equals(null)) { }   // throws NullPointerException if name is null

// Null-safe equality:
Objects.equals(s1, s2);      // null-safe: returns false if either is null
                              // returns true if both are null

Comparing Strings Correctly

String comparison deserves its own section because == on Strings is a pervasive bug in Java codebases. Always use .equals() or compareTo() for String comparison.
Java
String status = getStatus();   // returns "ACTIVE" from somewhere

// WRONG — may work sometimes (due to String pool), fails unpredictably:
if (status == "ACTIVE") { }

// CORRECT — always use .equals():
if (status.equals("ACTIVE")) { }

// BETTER — put the literal first to avoid NullPointerException:
if ("ACTIVE".equals(status)) { }   // safe even if status is null

// Case-insensitive comparison:
if ("active".equalsIgnoreCase(status)) { }

// Comparing String order (lexicographic):
String s1 = "Apple";
String s2 = "Banana";
int cmp = s1.compareTo(s2);
// returns negative if s1 < s2, 0 if equal, positive if s1 > s2
System.out.println(cmp);   // negative — 'A' comes before 'B'

// Case-insensitive order comparison:
int cmp2 = s1.compareToIgnoreCase(s2);

// Checking start, end, content:
String email = "user@example.com";
email.startsWith("user");     // true
email.endsWith(".com");       // true
email.contains("@");          // true

Comparing Numbers — Wrapper Pitfalls

When comparing Integer, Long, Double, or other wrapper objects, == compares references, not values. The Integer cache (-128 to 127) makes this even more confusing.
Java
// Integer cache — values -128 to 127 are cached:
Integer a = 100;
Integer b = 100;
System.out.println(a == b);       // true  — cached, same object
System.out.println(a.equals(b));  // true

Integer c = 200;
Integer d = 200;
System.out.println(c == d);       // false — outside cache, different objects
System.out.println(c.equals(d));  // true  — correct comparison

// Always use .equals() or compareTo() for wrapper types:
Integer x = 500;
Integer y = 500;
System.out.println(x.equals(y));           // true — correct
System.out.println(x.compareTo(y) == 0);   // true — correct

// Or unbox to primitives explicitly:
System.out.println((int) x == (int) y);    // true — comparing primitives

// Double comparison — .equals() checks exact bit representation:
Double d1 = 0.1 + 0.2;
Double d2 = 0.3;
System.out.println(d1.equals(d2));  // false — floating-point imprecision

Comparing Floating-Point Numbers

Never use == to compare floating-point values. Due to binary representation, decimal values like 0.1 and 0.2 cannot be stored exactly, so arithmetic results are rarely exactly equal to expected values.
Java
// The classic floating-point trap:
double a = 0.1 + 0.2;
double b = 0.3;
System.out.println(a == b);           // false!
System.out.println(a);                // 0.30000000000000004
System.out.println(b);                // 0.3

// Fix 1 — compare with an epsilon (tolerance):
double epsilon = 1e-10;
System.out.println(Math.abs(a - b) < epsilon);   // true

// Fix 2 — use BigDecimal for exact decimal arithmetic:
import java.math.BigDecimal;
BigDecimal bd1 = new BigDecimal("0.1").add(new BigDecimal("0.2"));
BigDecimal bd2 = new BigDecimal("0.3");
System.out.println(bd1.compareTo(bd2) == 0);   // true — exact

// Never use BigDecimal(double) — use BigDecimal(String):
new BigDecimal(0.1)     // 0.1000000000000000055511... — imprecise
new BigDecimal("0.1")   // 0.1 — exact

// Checking for special values:
double inf = 1.0 / 0.0;
double nan = 0.0 / 0.0;
System.out.println(inf == Double.POSITIVE_INFINITY);  // true
System.out.println(Double.isInfinite(inf));            // true
System.out.println(Double.isNaN(nan));                 // true
System.out.println(nan == nan);                        // false — NaN != NaN

Relational Operators with char

char values can be compared with relational operators because they are 16-bit integers under the hood. Comparison is based on Unicode code point values.
Java
// char comparison uses Unicode values:
char c1 = 'A';  // 65
char c2 = 'B';  // 66
char c3 = 'a';  // 97

System.out.println(c1 < c2);   // true65 < 66
System.out.println(c1 < c3);   // true65 < 97 (uppercase < lowercase)
System.out.println(c1 == 65);  // truechar compared to int literal

// Checking character ranges:
char ch = 'G';
boolean isUppercase = ch >= 'A' && ch <= 'Z';   // true
boolean isLowercase = ch >= 'a' && ch <= 'z';   // false
boolean isDigit = ch >= '0' && ch <= '9';       // false

// Or use Character class methods:
Character.isUpperCase('G');   // true
Character.isLowerCase('g');   // true
Character.isDigit('5');       // true
Character.isLetter('A');      // true

// char in switch (comparison via equality):
char grade = 'B';
switch (grade) {
    case 'A' -> System.out.println("Excellent");
    case 'B' -> System.out.println("Good");
    case 'C' -> System.out.println("Average");
    default  -> System.out.println("Below average");
}