الهوية مقابل المساواة
في هذا الدرس 11 موضوعات
هذا الدرس متاح حاليًا باللغة الإنجليزية فقط، ولم تُنشر نسخته العربية بعد.
Lesson Overview
Two banknotes can be the same note (same serial number) or merely equal in value. Java has the same two questions for objects, and it uses two different tools: the operator == asks about identity, and the method equals asks about equality. Mixing them up produces one of the most famous beginner bugs: comparing strings with ==.
By the end of this lesson you will know what each tool asks, why "bank" == "bank" can be true while new String("bank") == "bank" is false, and how to write comparisons that are correct every time. Earlier lessons told you to compare text with .equals; this is the full explanation.
Why this matters: in backend code, user input, command names, and status values are text. A comparison that works in your test and fails in production is the worst kind of bug.
This lesson builds on Lesson 1 (the default equals is identity) and on references and null from the objects module.
Learning Objectives
By the end of this lesson you will be able to:
- Explain what
==compares for primitives and for object references. - Explain what
equalsasks and what the default version does. - Predict
==andequalsfor strings built in different ways. - Explain why string literals are shared, and why that makes
==misleading. - Fix
command == "quit"and write thenull-safe form. - Choose between identity and equality for a class.
Written Explanation
Two questions
| Question | Tool | Banknote picture |
|---|---|---|
| Is this the very same object in memory? | == | Same serial number: the same note |
| Do these two objects have the same value? | equals | Different notes, same value |
For primitives such as int and double, == compares the values: 5 == 5 is true. For object references, == compares the references: it is true when both sides point at the same object (or both are null). It never looks inside the objects.
The default equals is identity
From Lesson 1: Object.equals returns true only for the same object. So for a class that does not override it:
Account x = new Account("A-1");
Account y = new Account("A-1");
Account z = x;
System.out.println(x == y); // false: two objects
System.out.println(x.equals(y)); // false: default equals is identity
System.out.println(x == z); // true: same object
System.out.println(x.equals(z)); // truez is not a copy: it is a second name for the same object, like two people holding the same note.
String overrides equals
The String class overrides equals to compare the characters. So a.equals(b) is true when both strings have the same text. That is the correct way to compare text. == on two strings still asks the identity question, and the answer depends on how each string was created.
Why == sometimes works on strings
The Java Language Specification says that string literals (and compile-time constant expressions) with the same text refer to the same String object: they are interned. So:
String a = "bank";
String b = "bank";
System.out.println(a == b); // true: both literals share one objectBut a string built at run time is a new object:
String c = new String("bank");
String suffix = "ank";
String d = "b" + suffix; // built at run time: suffix is a variable
System.out.println(a == c); // false
System.out.println(a == d); // false
System.out.println(a.equals(c)); // true
System.out.println(a.equals(d)); // trueThis is why == is dangerous: it appears to work in a quick test with literals and then fails when the text comes from a file, a network request, or a user typing. The rule is simple: never use == to compare the content of strings. (== on strings is legitimate only when you really mean identity, which is almost never.)
The null-safe comparison
If command could be null, then command.equals("quit") throws NullPointerException. Put the literal first:
if ("quit".equals(command)) { ... } // false when command is null, no exceptionA literal is never null, so the call is always safe. The java.util.Objects.equals(a, b) helper does the same for two variables: it returns true if both are null, false if only one is, and otherwise a.equals(b).
Why identity still matters
Identity is not a lesser tool. Two variables that point at the same object are aliases: a change made through one is visible through the other, because there is only one object. Asking x == z is how you check whether two variables are aliases, which is sometimes exactly the question (for example, "am I about to compare an object with itself?"). That is why equals implementations begin with if (this == o) return true;: it is a fast and correct shortcut, since an object is always equal to itself.
A quick checklist before you compare
- Are both sides primitives? Use
==. - Are they objects that represent values (strings, money, dates)? Use
equals, with the known value on the left if the other side may benull. - Do you genuinely mean "the same object"? Then
==is right, and it is worth a comment saying so.
Choosing identity or equality for your own class
- Entities with a life of their own (an open database connection, a thread, a specific bank account object being modified): identity may be right.
- Values (money, a date, a coordinate, an email address): two objects with the same data are interchangeable, so equality should compare the data. You do that by overriding
equals, which Lesson 3 teaches.
Examples
Example 1: strings built in different ways (simple)
String a = "bank";
String b = "bank";
String c = new String("bank");
String suffix = "ank";
String d = "b" + suffix;
System.out.println(a == b);
System.out.println(a == c);
System.out.println(a.equals(c));
System.out.println(a == d);
System.out.println(a.equals(d));Real output:
true
false
true
false
trueSame text in all four strings, but only a == b is true, because only those two are the same literal object. Every equals is true.
Example 2: objects with and without equality (practical)
Account x = new Account("A-1");
Account y = new Account("A-1");
Account z = x;
System.out.println(x == y);
System.out.println(x.equals(y));
System.out.println(x == z);
System.out.println(x.equals(z));false
false
true
truex and y carry identical data but are two objects, and Account does not override equals. Before you complain that this is wrong, ask what the business means: are two account objects with the same number the same account? Probably yes, and that decision is yours to code in Lesson 3.
Example 3: a command loop bug (realistic)
A console tool reads a command from the user and exits on quit. The buggy version:
String word = new String("quit"); // stands in for text that arrived at run time
if (word == "quit") System.out.println("== matched"); else System.out.println("== missed");
if ("quit".equals(word)) System.out.println("equals matched");== missed
equals matchedWith the test string typed as a literal the == version would have printed "matched" and passed your tests. With real input it misses, so the program never quits. The fix is "quit".equals(word), which is also safe if word is null:
String command = null;
System.out.println("quit".equals(command)); // false, no exception
command.equals("quit"); // throws NullPointerExceptionBackend Insight
A practical rule for code reviews: any == between two non-primitive values deserves a second look. Either it compares against null (x == null, which is correct and common), or it compares enum-like constants you defined yourself, or it is a bug. A reviewer who asks "what do you mean here, same object or same value?" prevents many production defects.
Text comparison is everywhere in a backend: HTTP method names, header values, role names such as "ADMIN", status codes in a database column. All of these arrive as strings built at run time, so == would fail on them even when they look right in the log. Comparing with "ADMIN".equals(role) is the habit that survives production.
Also think about what kind of equality your business needs. Are two Payment objects with the same id the same payment? Are two Money objects with the same amount equal? These questions decide whether duplicates are detected in a collection, whether a cache finds a record, and whether a retry creates a second charge. You will implement the answers in the next lessons.
Common Mistakes
Mistake 1: Comparing strings with ==
- Incorrect:
if (command == "quit") - Correct:
if ("quit".equals(command))
Mistake 2: Trusting a test that used literals
A test where both strings are literals will pass with ==. Use equals always, so the code is correct whatever the origin of the text.
Mistake 3: Calling equals on a possibly null variable
command.equals("quit") throws NullPointerException when command is null. Put the known value first, or use Objects.equals(a, b).
Mistake 4: Comparing with == and "fixing" it by trial
Some beginners flip between == and equals until the test turns green. Instead, ask which question you mean (same object, or same value) and write the tool that asks it.
Mistake 5: Expecting equals to work on your own class without overriding it
For a class that did not override equals, a.equals(b) is identity. Two objects with identical fields are still "not equal".
Exercises
Try each exercise yourself before opening the solutions.
Level A: Foundation
A1. What does == compare when both sides are int? When both sides are object references?
أظهر الحل A1
A1. For int it compares the numbers. For object references it asks whether both point at the same object (or are both null).
A2. Say true or false for "a" == "a" and "a".equals("a").
أظهر الحل A2
A2. Both are true. The first is true only because string literals with the same text share one object; the second compares text.
A3. Rewrite if (answer == "yes") correctly.
أظهر الحل A3
A3. if ("yes".equals(answer))
Level B: Application
B1. With String s = new String("go");, give the result of s == "go" and s.equals("go"), and explain the difference in one sentence.
أظهر الحل B1
B1. s == "go" is false and s.equals("go") is true. new String(...) creates a separate object, so identity differs, but the characters are the same.
B2. Account p = new Account("A-1"); Account q = p; (no overrides). What are p == q and p.equals(q)? What if q = new Account("A-1")?
أظهر الحل B2
B2. With q = p, both are true (same object). With q = new Account("A-1"), both are false, because == compares identity and the default equals does too.
B3. role may be null. Write a check that is true only when role is the text ADMIN, without ever throwing.
أظهر الحل B3
B3. "ADMIN".equals(role). Or Objects.equals(role, "ADMIN").
Level C: Challenge
C1. A teammate says: "My == string comparison works in all my unit tests, so it is fine." Explain why the tests mislead and give a code line that would make the same comparison fail.
أظهر الحل C1
C1. In tests the strings are usually literals, which Java shares, so == accidentally returns true. In production the text comes from input, so it is a new object. For example String x = new String("quit"); x == "quit" is false even though the text is the same.
C2. Decide for each: identity or equality for a class Money(cents, currency), a class Printer that represents one physical printer, a class EmailAddress(text). Give one sentence of reasoning each.
أظهر الحل C2
C2. Money: equality, 100 SAR is the same value wherever it appears. Printer: identity, each object stands for one physical device, two printers with the same model are still different. EmailAddress: equality, two objects holding the same text are interchangeable.
Mini Challenge
Write a small program with a method boolean isStop(String input) that returns true when input is exactly the text stop and is safe for null. Call it with a literal, with new String("stop"), with null, and with "Stop". Print each result.
Check your work: the first two return true, null returns false without crashing, and "Stop" returns false because equals is case sensitive.
Summary & Next Lesson
Key takeaways
==on primitives compares values. On references it asks "same object?".equalsasks "same value?"; the default version fromObjectis identity.Stringoverridesequals, so text is compared withequals, never with==.- String literals are shared, so
==can seem to work and then fail on text built at run time. - Write
"quit".equals(command)to be safe againstnull.
Next lesson
The next lesson, "The equals/hashCode contract", shows how to override equals correctly for your own value classes, and why hashCode must always change with it.
Self-check
- What does
==ask about two objects? - Why does
new String("a") == "a"give false? - Which form is
null-safe:x.equals("a")or"a".equals(x)? - What does the default
equalsdo? - Why can
==on strings pass tests and still be wrong?
أظهر الإجابات
- Whether both references point to the very same object.
new Stringcreates a new object, different from the shared literal."a".equals(x).- It behaves like
==(identity). - Literals are shared, so tests with literals match; run-time strings are separate objects.
References
- Oracle, Java Language Specification SE 21, chapter 3: Lexical Structure. Section 3.10.5 says string literals and constant string expressions with the same content refer to the same interned
Stringobject, while strings built at run time are new objects. - Oracle, Java SE 21 API:
java.lang.Object. It states that the defaultequalsis the identity relation. - Oracle, Java SE 21 API:
java.util.Objects. It describesObjects.equalsas safe when either argument isnull. - Oracle, Java SE 21 API:
java.lang.String. It saysString.equalsis true only for aStringwith the same sequence of characters, and false fornull. - That
==on references compares the references was not verified on the JLS 15.21.3 page itself (the page text was too large to read through the fetch tool); it follows from the examples above, which were compiled and run on JDK 21.
التطبيق
طبّق هذا الدرس بمختبر الوحدة وتمارينها واختبارها.
تعليم الدرس كمكتمل اختياري ويمكنك التراجع عنه في أي وقت. يبقى تقدمك محفوظًا في هذا المتصفح.