Why Objects.equals Can Mislead You: Hidden Pitfalls and How to Avoid Them
This article examines why Java's Objects.equals may return unexpected results, explores the differences between ==, equals, and Objects.equals, highlights null‑pointer risks, and reveals common type‑mismatch pitfalls with practical code examples and solutions.
Introduction
While reviewing a teammate's code I discovered a case where Objects.equals returned false even though the two values looked identical, prompting a deeper investigation.
1. Incident Scene
A requirement checks whether the currently logged‑in user is the system administrator (userId = 888) and sends an email if true. The naive implementation uses:
UserInfo userInfo = CurrentUser.getUserInfo();
if (Objects.isNull(userInfo)) {
log.info("请先登录");
return;
}
if (Objects.equals(userInfo.getId(), 888)) {
sendEmail(userInfo);
}The UserInfo class defines id as Long. Because the literal 888 is an int, the comparison fails.
2. Methods for Equality Comparison
2.1 Using ==
For primitive types the == operator compares values directly. For wrapper objects it compares references, which can be surprising:
int a = 1;
int b = 1;
byte c = 1;
Integer d1 = new Integer(1);
Integer d2 = new Integer(1);
System.out.println(a == b); // true
System.out.println(a == c); // true
System.out.println(a == d1); // true (auto‑unboxing)
System.out.println(d1 == d2); // false (different objects)When an Integer is compared with an int , auto‑unboxing occurs, so the values are compared.
Wrapper objects also use a constant pool for values between -128 and 127, which explains why Integer i1 = 1; Integer i2 = 1; yields true while Integer i3 = 128; Integer i4 = 128; yields false.
2.2 Using equals Method
The Object.equals method only checks reference equality:
public boolean equals(Object obj) {
return (this == obj);
}String overrides equals to compare character arrays element‑by‑element, allowing value equality for distinct objects:
public boolean equals(Object anObject) {
if (this == anObject) return true;
if (anObject instanceof String) {
String anotherString = (String) anObject;
int n = value.length;
if (n == anotherString.value.length) {
char[] v1 = value;
char[] v2 = anotherString.value;
for (int i = 0; i < n; i++) {
if (v1[i] != v2[i]) return false;
}
return true;
}
}
return false;
}Thus "abc".equals(new String("abc")) returns true even though the references differ.
3. NullPointerException
Both == and equals can trigger NullPointerException when one operand is null and auto‑unboxing is required:
int a = 1;
Integer c = null;
System.out.println(a == c); // NullPointerException (auto‑unboxing null)
String e = null;
String f = "abc";
System.out.println(e.equals(f)); // NullPointerExceptionGuarding against null before calling equals solves the problem:
private static boolean equals(String e, String f) {
if (e == null) return f == null;
return e.equals(f);
}4. Purpose of Objects.equals
The utility method in java.util.Objects safely compares two objects:
public static boolean equals(Object a, Object b) {
return (a == b) || (a != null && a.equals(b));
}It first checks reference equality, then handles the a == null case, and finally delegates to a.equals(b). This avoids NullPointerException while preserving logical equality.
5. Pitfalls of Objects.equals
When the two arguments have different wrapper types, Objects.equals may return false even if the numeric values are equal:
Integer a = 1;
long b = 1L;
System.out.println(Objects.equals(a, b)); // false
System.out.println(a == b); // true (auto‑unboxing)The reason is that Integer.equals only returns true if the argument is also an Integer. The same applies to Long.equals, Byte.equals, etc.
Therefore, when using Objects.equals , ensure both parameters are of the same type; otherwise the method will incorrectly report inequality.
Common scenarios where this pitfall appears include:
Comparing Long user IDs with Integer values.
Comparing Byte status codes with Integer values.
Comparing Double amounts with Integer zero checks.
Solutions are to cast one operand to the other's type or to use primitive comparison with == after unboxing.
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 Backend Technology
Focus on Java-related technologies: SSM, Spring ecosystem, microservices, MySQL, MyCat, clustering, distributed systems, middleware, Linux, networking, multithreading. Occasionally cover DevOps tools like Jenkins, Nexus, Docker, and ELK. Also share technical insights from time to time, committed to Java full-stack development!
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.
