Back to Blog
Java

Java == Operator: Reference vs Value Comparison

java == operator: Understand how the Java == operator compares primitives by value and objects by reference, and when to use equals() instead.

javaequalityreference comparisonequals methodprimitive types
Diagram showing Java == operator comparing two object references and two primitive values

The java == operator compares primitives by value and objects by reference. This distinction is the source of many subtle bugs in Java code, especially when strings or wrapper types are involved. Understanding exactly what == does in each situation is essential for writing correct comparisons.

How == Works for Primitive Types

For primitive types such as int, char, boolean, and double, == compares the actual values. Two primitive variables are equal if they hold the same value. This is straightforward and matches the intuitive notion of equality.

int a = 5; int b = 5; System.out.println(a == b); // true char c1 = 'x'; char c2 = 'x'; System.out.println(c1 == c2); // true

One subtlety: floating-point primitives follow IEEE 754 semantics. Double.NaN is not equal to itself, and -0.0 is equal to 0.0 under ==. These edge cases rarely affect typical applications but are worth remembering if you work with scientific or numerical code.

How == Works for Reference Types

When applied to reference types (objects, arrays, and interfaces), == compares the memory addresses of the references. It checks whether both variables point to the exact same object instance, not whether the objects are logically equivalent. Two distinct objects with identical fields are not equal under ==.

String s1 = new String("hello"); String s2 = new String("hello"); System.out.println(s1 == s2); // false, different objects String s3 = s1; System.out.println(s1 == s3); // true, same reference

This behavior is often called reference equality. It is useful when you need to confirm that two variables refer to the same instance, such as when checking for a shared cache entry or a singleton object.

The Difference Between == and equals()

The equals() method, defined in Object, is intended for logical equality. By default, Object.equals() behaves like ==, but many classes override it to compare the contents of objects. For example, String.equals() compares the character sequence, and Integer.equals() compares the wrapped int value.

String s1 = new String("hello"); String s2 = new String("hello"); System.out.println(s1.equals(s2)); // true, same characters

When you create a custom class, you should override equals() to define what logical equality means for your objects. The contract also requires overriding hashCode() so that equal objects produce the same hash code. Without a proper override, collections like HashSet and HashMap will not behave correctly.

Common Pitfalls with == for Strings and Wrapper Types

Java's string interning mechanism caches string literals. As a result, == often works for literals but fails for dynamically created strings.

String a = "hello"; String b = "hello"; System.out.println(a == b); // true, both refer to the interned literal String c = new String("hello"); System.out.println(a == c); // false, c is a new object

Relying on interning is fragile because it depends on how the strings were created. Always use equals() for string content comparison.

Wrapper classes like Integer also have a caching mechanism. Values between -128 and 127 are cached, so == returns true for those values but false outside the range.

Integer x = 100; Integer y = 100; System.out.println(x == y); // true, cached Integer p = 200; Integer q = 200; System.out.println(p == q); // false, not cached

This behavior is version-dependent and easy to forget. For wrapper types, use .equals() or unbox to a primitive when comparing values.

When to Use == vs equals() in Practice

Use == for:

  • Primitive value comparisons.
  • Reference identity checks, such as verifying that two variables point to the same object.
  • Null checks: if (obj == null).
  • Comparing enum constants, since enums are singletons and == is safe.

Use equals() for:

  • Comparing strings and wrapper types by value.
  • Comparing custom objects where logical equality is defined.
  • Comparing collections, dates, and other domain objects.

In general, if you are not sure whether an object overrides equals(), prefer equals() unless you specifically need reference identity.

Performance and Runtime Considerations

== is a single JVM instruction that compares primitive values or reference addresses directly. It has essentially no overhead. equals() is a method call, and its cost depends on the implementation. For example, String.equals() first checks reference equality, then length, then character-by-character comparison. This is still fast, but it is not free.

In practice, the performance difference between == and equals() is negligible unless you are performing millions of comparisons in a tight loop. The larger risk is using == where logical equality is required, which leads to incorrect behavior that is difficult to trace. Correctness should always take priority over micro-optimizations.

Null Checks and ==

The == operator is the idiomatic way to test for null in Java. Because it compares references, it works for any object type.

String name = getName(); if (name == null) { System.out.println("No name provided"); }

You can also use == to compare an object to a known constant, such as an enum value. Since enums are singletons, reference equality is guaranteed.

Status status = getStatus(); if (status == Status.ACTIVE) { // handle active state }

Using equals() for null checks would throw a NullPointerException unless you call it on a non-null reference. Therefore, == remains the safest and clearest way to check for null.

java == operator: Practical Usage and Code Examples | RYUSLOG DEV