☕ 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 nullComparing 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("@"); // trueComparing 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 imprecisionComparing 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 != NaNRelational 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); // true — 65 < 66
System.out.println(c1 < c3); // true — 65 < 97 (uppercase < lowercase)
System.out.println(c1 == 65); // true — char 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");
}Related Topics in Java Basics
Variables in Java
A variable is just a named box in memory that holds a value. Java is strict about what goes in each box — you tell it the type upfront. Once you get this, the rest of Java clicks into place.
Data Types in Java
Java needs to know exactly what kind of data it's dealing with before it can store or process it. Integers, decimals, characters, true/false — each has its own type. Knowing which to use (and why) makes your programs efficient and bug-free.
Primitive Data Types
Java has eight primitive data types — the most basic building blocks for storing data. Unlike objects, primitives are stored directly in memory, making them fast and efficient. Understanding each type, its size, range, and when to use it is fundamental to writing correct Java programs.
Non-Primitive Data Types
Non-primitive data types — also called reference types — are everything beyond Java's eight primitives. Strings, arrays, classes, interfaces, enums, and records all fall into this category. They're more powerful than primitives, but they work differently in memory, comparison, and nullability. Understanding the distinction is essential for writing correct Java.