Chapter 2 Flashcards — Methods Common to All Objects
flashcards effective-java equals hashcode tostring comparable clone java
What are the five properties of the equals general contract?
?
- Reflexive:
x.equals(x)is alwaystrue. - Symmetric:
x.equals(y)iffy.equals(y)— if x says it equals y, y must say it equals x. - Transitive: if
x.equals(y)andy.equals(z), thenx.equals(z). - Consistent: repeated calls with no modification return the same result.
- Non-null:
x.equals(null)is alwaysfalse.
When should you override equals?
?
Override equals when the class has a notion of logical equality that differs from object identity — i.e., value classes (classes representing values like Point, Money, PhoneNumber, LocalDate). Do NOT override when: object identity is the right semantics (threads, connections), a superclass already does it correctly, or the class is a private/package-private implementation detail.
Why does if (!(o instanceof Foo)) handle the null check inside equals?
?
Because null instanceof Foo is always false — regardless of the type. So the single instanceof check handles both: (1) o is null, and (2) o is the wrong type. No separate if (o == null) return false; is needed.
What is the canonical recipe for a correct equals implementation?
?
@Override public boolean equals(Object o) {
if (o == this) return true; // 1. reference check (optimization)
if (!(o instanceof MyClass mc)) return false; // 2. type + null check; bind mc (Java 16+)
return mc.field1 == field1 // 3. compare all significant fields
&& Objects.equals(mc.field2, field2);
}Use == for primitives, Float.compare/Double.compare for float/double, Objects.equals for objects (null-safe).
Why is it impossible to extend an instantiable class, add a value component, and preserve the equals contract?
?
Because of the Liskov Substitution Principle: any instance of a subclass must behave as an instance of the superclass in all contexts. If ColorPoint extends Point, then colorPoint.equals(point) must behave symmetrically with point.equals(colorPoint). Achieving symmetry requires ignoring color in one direction, which breaks transitivity when two ColorPoints of different colors both equal the same Point.
The fix: use composition — ColorPoint contains a Point field and provides a view method asPoint().
What is the correct fix when you need to add a value component to a class that already overrides equals?
?
Use composition over inheritance: have the new class contain an instance of the original class as a field, and add a view method that returns it.
public class ColorPoint {
private final Point point; // composition, not extension
private final Color color;
public Point asPoint() { return point; } // view method
@Override public boolean equals(Object o) {
if (!(o instanceof ColorPoint cp)) return false;
return cp.point.equals(point) && cp.color.equals(color);
}
}What is the general contract for hashCode?
?
- Consistent: within one JVM execution,
hashCode()always returns the same value if noequals-relevant state changes. - Equal objects must have equal hash codes: if
x.equals(y), thenx.hashCode() == y.hashCode(). - Unequal objects may have equal hash codes (collisions are allowed), but distinct hash codes improve performance.
The most critical rule: always override hashCode when you override equals.
What breaks if you override equals but not hashCode?
?
All hash-based collections break silently: HashMap, HashSet, LinkedHashMap, Hashtable.
Example:
Map<PhoneNumber, String> m = new HashMap<>();
m.put(new PhoneNumber(707, 867, 5309), "Jenny");
m.get(new PhoneNumber(707, 867, 5309)); // returns null — wrong hash bucket!equals says the keys are equal, but hashCode returns different values (from Object), so the map looks in the wrong bucket and finds nothing.
How do you implement a good hashCode manually?
?
Use the 31-multiplication recipe:
@Override public int hashCode() {
int result = Type.hashCode(field1); // start with most significant field
result = 31 * result + Type.hashCode(field2);
result = 31 * result + Objects.hashCode(field3); // null-safe for objects
return result;
}Why 31? It is an odd prime; 31 * i = (i << 5) - i which the JIT optimizes to a fast shift-and-subtract.
What is the shortcut for implementing hashCode in Java 7+?
?
Objects.hash(field1, field2, ...) — computes a combined hash code for all supplied objects. Slightly slower due to varargs array creation and autoboxing, but convenient for non-performance-critical code:
@Override public int hashCode() {
return Objects.hash(areaCode, prefix, lineNum);
}When should you cache the hash code, and how?
?
For immutable classes where hashing is expensive (e.g., large string fields), cache the result in a private int hashCode field (default 0). Use lazy initialization:
private int hashCode; // lazily computed, 0 = not yet computed
@Override public int hashCode() {
int result = hashCode;
if (result == 0) {
result = Objects.hash(expensiveField1, expensiveField2);
hashCode = result;
}
return result;
}Note: 0 as sentinel works because a legitimate hash code of 0 will simply be recomputed once.
What should toString return?
?
A concise, human-readable representation of all significant information about the object — the same state used in equals. Good toString makes logs, error messages, and debugger output immediately actionable. For value classes, ideally match a documented format. Always provide programmatic accessors for all fields mentioned in toString, so callers do not have to parse the string.
What are the two toString format documentation strategies?
?
- Specify the format: document exactly what
toStringreturns (e.g.,"(XXX) XXX-XXXX"). Commit to it — clients may depend on it. Provide a matching parser/factory for round-tripping. - Leave unspecified: document that the format may change (e.g., “typical representation:
[Potion: type=love]”). Gives flexibility to improve the representation later.
Either way: provide getters for all fields in the string so callers do not have to parse it.
How do records (Java 16+) handle toString?
?
Records auto-generate a toString that includes all record components in the form TypeName[field1=value1, field2=value2]:
record Point(int x, int y) {}
new Point(3, 4).toString(); // "Point[x=3, y=4]"You should not override it unless you need a specific format. The generated toString is always correct and informative.
What is String.formatted() (Java 15+) and how does it improve toString implementations?
?
String.formatted(args...) is an instance method equivalent to String.format(this, args...) — cleaner call site:
// Before Java 15
return String.format("(%03d) %03d-%04d", areaCode, prefix, lineNum);
// Java 15+
return "(%03d) %03d-%04d".formatted(areaCode, prefix, lineNum);Also, text blocks (Java 15+) help with multi-line toString for complex objects.
What are the fundamental problems with Cloneable and Object.clone()?
?
- Bypasses constructors —
super.clone()creates an object without calling any constructor; invariants and instance counting break. - Shallow copy by default — copies field references, not referenced objects; mutable state is aliased between original and clone.
- Cannot work with
finalmutable fields — cannot assign to final fields outside a constructor. - Spurious checked exception —
CloneNotSupportedExceptionmust be caught even when it cannot occur. - No compile-time enforcement — implementing
Cloneable(a marker interface) does not guarantee a correct or even accessibleclone().
What is the correct alternative to Cloneable for copying objects?
?
Copy constructor or copy factory (static):
// Copy constructor — uses normal construction, works with finals, no checked exception
public Stack(Stack source) {
this.elements = source.elements.clone(); // array clone is always fine
this.size = source.size;
}
// Copy factory
public static Stack copyOf(Stack source) { return new Stack(source); }These go through the normal constructor, enforce invariants, work with final fields, do not throw spurious exceptions, and can accept supertypes (conversion constructors).
When is array.clone() the right tool?
?
Array cloning is always correct and idiomatic — arrays implement Cloneable and clone() returns a properly typed array with all elements copied. Use it whenever you need to copy an array field in a deep clone:
result.elements = elements.clone(); // correct: independent copy of the arrayThis is the one context where clone() is safe and idiomatic (even in classes that otherwise avoid Cloneable).
What is the Comparable interface and what does compareTo return?
?
Comparable<T> has one method: int compareTo(T o). It returns:
- Negative if
this<o - Zero if
this==o(by natural ordering) - Positive if
this>o
Implementing Comparable enables interoperation with TreeSet, TreeMap, Arrays.sort(), Collections.sort(), PriorityQueue, and all ordering-based algorithms — for free.
What are the three properties of the compareTo general contract?
?
- Antisymmetric:
sign(x.compareTo(y)) == -sign(y.compareTo(x))— if x < y then y > x. - Transitive: if
x.compareTo(y) > 0andy.compareTo(z) > 0, thenx.compareTo(z) > 0. - Consistent with equals (strongly recommended):
(x.compareTo(y) == 0) == x.equals(y)— if compareTo says equal, equals should agree.
What is “consistent with equals” for compareTo, and why does BigDecimal violate it?
?
“Consistent with equals” means: compareTo returns 0 if and only if equals returns true.
BigDecimal violates this intentionally: new BigDecimal("1.0").compareTo(new BigDecimal("1.00")) == 0 (same numeric value), but new BigDecimal("1.0").equals(new BigDecimal("1.00")) is false (different scale).
Consequence: HashSet of these two holds 2 elements; TreeSet holds 1. Same objects, different behavior depending on which Set you use. Always document such inconsistencies.
Why is the subtraction anti-pattern for compareTo dangerous?
?
Integer overflow: a - b overflows when a is large positive and b is large negative (or vice versa), producing the wrong sign:
// WRONG — can overflow: if a=Integer.MAX_VALUE and b=-1, result is negative (WRONG order!)
return a - b;
// CORRECT — always use compare methods
return Integer.compare(a, b); // or Long.compare, Double.compare, etc.This bug can cause sort algorithms to produce incorrect orderings that appear random.
What is the preferred way to implement compareTo for a class with multiple sort keys?
?
Use Comparator construction methods (Java 8+) for conciseness and correctness:
private static final Comparator<PhoneNumber> COMPARATOR =
Comparator.comparingInt((PhoneNumber pn) -> pn.areaCode)
.thenComparingInt(pn -> pn.prefix)
.thenComparingInt(pn -> pn.lineNum);
@Override public int compareTo(PhoneNumber pn) {
return COMPARATOR.compare(this, pn);
}Use comparingInt, comparingLong, comparingDouble for primitives (avoids autoboxing). Use comparing(keyExtractor) for objects.
How do you sort a collection in descending order using Comparator?
?
Use .reversed() on a Comparator:
// Ascending by salary
Comparator<Employee> asc = Comparator.comparingDouble(Employee::salary);
// Descending by salary
Comparator<Employee> desc = asc.reversed();
// Chain: descending salary, then ascending name
Comparator<Employee> comp = Comparator.comparingDouble(Employee::salary)
.reversed()
.thenComparing(Employee::name);How does pattern matching for instanceof (Java 16+) improve equals?
?
Before Java 16, you needed two steps — type check then cast:
if (!(o instanceof PhoneNumber)) return false;
PhoneNumber pn = (PhoneNumber) o; // redundant castJava 16+ combines them with a pattern variable:
if (!(o instanceof PhoneNumber pn)) return false;
// pn is bound and in scope here — no cast needed
return pn.lineNum == lineNum && ...;More concise, type-safe, and eliminates an unchecked cast.
Do records (Java 16+) implement Comparable automatically?
?
No. Records auto-generate equals, hashCode, and toString for all components, but do not implement Comparable. If you need natural ordering for a record, implement it manually:
public record Employee(String name, double salary) implements Comparable<Employee> {
@Override public int compareTo(Employee other) {
return Comparator.comparing(Employee::name)
.thenComparingDouble(Employee::salary)
.compare(this, other);
}
}What is the difference between Comparable and Comparator?
?
Comparable<T>: implemented by the class being compared; defines the natural ordering. One ordering per class. Used byTreeSet,Arrays.sort()by default.Comparator<T>: an external ordering strategy; can define multiple orderings for the same class. Passed toTreeSet(Comparator),List.sort(Comparator),Arrays.sort(array, Comparator).
When a class has one obvious natural ordering, implement Comparable. When multiple orderings exist (e.g., sort by name OR by salary), define named Comparator constants.
How does Objects.equals(a, b) differ from a.equals(b)?
?
Objects.equals(a, b) is null-safe: returns true if both are null, false if exactly one is null, and delegates to a.equals(b) otherwise. Preferred in equals implementations when fields can be null:
// Null-safe comparison in equals
Objects.equals(this.name, other.name) // safe even if name is null
// vs.
this.name.equals(other.name) // NullPointerException if name is null!Why should you check o == this first in an equals implementation?
?
It is a performance optimization. If both references point to the same object, they are definitely equal — no need to compare individual fields. For objects with many fields or expensive comparisons, this short-circuit can significantly speed up operations like contains() in a collection that is checking if an already-contained object is present.
What is the final field problem with clone?
?
Object.clone() creates a new instance without calling a constructor. If your class has final mutable fields (e.g., final int[] data), the clone will share the same array object. You cannot fix this in clone() by reassigning the field because final fields cannot be assigned outside of constructors. This makes it fundamentally impossible to write a correct deep-clone for classes with final mutable fields using the Cloneable mechanism.
Solution: copy constructors/factories — they are constructors, so they can set final fields.
What does Throwable.getSuppressed() return and when is it populated?
?
getSuppressed() returns the suppressed exceptions attached to a Throwable. In try-with-resources, if both the body and close() throw, the body exception propagates normally and the close() exception is suppressed (attached to the body exception via addSuppressed()). Callers can inspect suppressed exceptions:
try (Resource r = new Resource()) { r.work(); }
catch (Exception e) {
// e is the body exception; e.getSuppressed() contains the close() exception
Arrays.stream(e.getSuppressed()).forEach(System.err::println);
}This ensures no exception is silently lost, unlike try-finally where the finally exception replaces the body exception.
Total Cards: 30
Review Time: ~18 minutes
Priority: HIGH
Last Updated: 2026-05-10