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.
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
trueWhat == 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); // trueReference 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); // trueThe 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 errorFor 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 falseSimplified flow of String.equals():
call s1.equals(s3)
├─ if (this == obj) return true
├─ if (!(obj instanceof String)) return false
├─ compare lengths
└─ compare each characterInterview 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.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
