Fundamentals 16 min read

Do You Really Understand the Difference Between == and equals() in Java?

This article explains how == compares primitive values and object references, why Object.equals() defaults to ==, how classes like String and Integer override equals(), the impact of the string constant pool, integer caching, auto‑boxing, floating‑point pitfalls, and best practices for correctly comparing objects in Java.

Java Tech Workshop
Java Tech Workshop
Java Tech Workshop
Do You Really Understand the Difference Between == and equals() in Java?

Confusing example

The following program prints the results of == and equals() for several String objects:

public class EqualsTest {
    public static void main(String[] args) {
        String s1 = "hello";
        String s2 = "hello";
        String s3 = new String("hello");
        String s4 = new String("hello");
        System.out.println(s1 == s2); // 1
        System.out.println(s1 == s3); // 2
        System.out.println(s3 == s4); // 3
        System.out.println(s1.equals(s2));
        System.out.println(s1.equals(s3));
        System.out.println(s3.equals(s4));
    }
}

Output:

true
false
false
true
true
true

What == compares

Primitive types

For the eight primitive types ( byte, short, int, long, float, double, char, boolean) the operator == directly compares the stored value because primitives are not objects and have no reference.

int a = 100;
int b = 100;
System.out.println(a == b); // true

double d1 = 3.14;
double d2 = 3.14;
System.out.println(d1 == d2); // true

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

Reference types

Reference types (classes, interfaces, arrays, enums) store a memory address. == checks whether two references point to the exact same object on the heap.

String str1 = new String("java");
String str2 = new String("java");
System.out.println(str1 == str2); // false
String str3 = str1;
System.out.println(str1 == str3); // true

The truth about equals()

Default implementation in Object

public boolean equals(Object obj) {
    return (this == obj);
}

Without overriding, equals() behaves exactly like ==, comparing object identity.

Why String.equals() compares content

public boolean equals(Object anObject) {
    if (this == anObject) return true;               // identity check
    if (anObject instanceof String) {
        String anotherString = (String) anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            for (int i = 0; i < n; i++) {
                if (value[i] != anotherString.value[i]) return false;
            }
            return true;
        }
    }
    return false;
}

The method first checks identity, then verifies the argument is a String, compares lengths, and finally compares each character.

Common classes that override equals()

String – compares character content

Integer, Long – compare the wrapped numeric value

Double – compare the wrapped double value

BigDecimal – compare precise decimal value

Date – compare the timestamp

List, Set, Map – compare elements/entries using their own

equals()

Interview pitfalls

String constant pool

String s1 = "java";
String s2 = "java";
System.out.println(s1 == s2); // true (same pool entry)
String s3 = new String("java");
System.out.println(s1 == s3); // false (heap object)

Literal strings are interned in the JVM’s string constant pool; identical literals share the same object. Using new forces a distinct heap allocation.

Integer cache

Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2); // true (cached)
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4); // false (outside cache)

Integer caches values in the range -128 to 127. The cache size can be changed with the JVM option -XX:AutoBoxCacheMax=<size>.

Auto‑boxing and auto‑unboxing

Integer i = 100; // auto‑boxing
int j = 100;    // primitive
System.out.println(i == j);      // true (i unboxed)
System.out.println(i.equals(j)); // false (j boxed to Integer)

When both operands are primitives, == compares values. When both are objects, == compares references.

Floating‑point precision

Double d1 = 0.1;
Double d2 = 0.1;
System.out.println(d1.equals(d2)); // true
System.out.println(0.1 + 0.2 == 0.3); // false due to binary representation error

For exact decimal calculations, use BigDecimal.

List.contains()

List<String> list = new ArrayList<>();
String s1 = new String("java");
list.add(s1);
System.out.println(list.contains("java"));          // false
System.out.println(list.contains(s1));               // true
System.out.println(list.contains(new String("java"))); // false
contains()

internally calls equals(). Because the list stores the exact object reference s1, only an element that is equals() to s1 is found.

How to compare objects correctly

Recommended comparison methods

Primitive types – use == (no equals() method)

String – use equals() to compare content

Wrapper types (Integer, Long, …) – use equals() (overridden to compare value)

Custom objects – override equals() to define business‑level equality

Reference identity – use == when you need to know whether two references point to the same object (faster)

Why override equals()

Two Person instances with identical fields should be considered equal, but the default Object.equals() would return false because they are different instances.

public class Person {
    private String name;
    private int age;
    private String idCard;
    // getters/setters omitted
}
Person p1 = new Person("Zhang San", 25, "110101199001011234");
Person p2 = new Person("Zhang San", 25, "110101199001011234");
System.out.println(p1 == p2);      // false
System.out.println(p1.equals(p2)); // false (default)

Steps to correctly override equals()

@Override
public boolean equals(Object obj) {
    if (this == obj) return true;                         // identity
    if (obj == null || getClass() != obj.getClass()) return false; // null & type check
    Person person = (Person) obj;                         // cast
    return age == person.age &&
           Objects.equals(name, person.name) &&
           Objects.equals(idCard, person.idCard);
}

Note the use of getClass() for strict type matching instead of instanceof.

Contract with hashCode()

If two objects are equal, their hashCode() must be identical; unequal objects may share a hash code.
@Override
public int hashCode() {
    return Objects.hash(name, age, idCard);
}

IDE‑generated implementations are the safest way to keep the contract.

Deep dive into JVM memory

Runtime data areas:

+---------------------------+
|   Stack (primitive & ref) |
+---------------------------+
|   Heap (objects, arrays)  |
+---------------------------+
|   Method Area (class info, static fields, string pool) |
+---------------------------+

How == works in memory:

int a = 10; // stack value 10
int b = 10; // stack value 10
// a == b compares 10 and 10

String s1 = "hello"; // stack reference -> method‑area constant pool
String s2 = "hello"; // same pool entry, so s1 == s2 is true
String s3 = new String("hello"); // heap object, s1 == s3 is false

Simplified flow of String.equals():

call s1.equals(s3)
 ├─ if (this == obj) return true
 ├─ if (!(obj instanceof String)) return false
 ├─ compare lengths
 └─ compare each character

Interview formula

Distinguish primitive vs reference types.

State that Object.equals() defaults to ==.

Explain that classes like String, Integer override equals() to compare content.

Give concrete examples (String literals vs new, Integer cache).

Mention string constant pool and Integer cache effects on ==.

Highlight the equals()hashCode() contract.

Conclusion

The difference between == and equals() is not merely “reference vs value”. It involves JVM memory layout, class‑specific overrides, caching mechanisms, and the need to follow the equals()hashCode() contract when defining custom equality. Mastering these details demonstrates deep Java expertise.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Javainterview==equals()reference typesprimitive typesstring poolinteger cache
Java Tech Workshop
Written by

Java Tech Workshop

Focused on Java backend technologies, sharing fundamentals, multithreading, JVM, the Spring ecosystem, microservices, distributed systems, high concurrency, source‑code analysis, and practical experience. Continuously delivers high‑quality original content, interview guides, and learning roadmaps to help Java developers progress from beginner to advanced, enhancing technical skills and core competitiveness.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.