Equalities
Equalities#
Object equality determines whether two objects are considered "the same" based on specific criteria. This comparison can be based on object identity (reference equality) or content similarity (value equality).
Types of Equality#
Reference Equality: Are these the exact same object? (same memory location)
Value Equality: Do these objects have the same meaningful characteristics? (functionally equivalent)
Equality Concepts#
graph LR
A[Object Equality] --> B[Reference Equality]
A --> C[Value Equality]
B --> D["Same memory location"]
C --> E["Same meaningful properties"]
style A fill:#e3f2fd
style B fill:#ffcdd2
style C fill:#c8e6c9
Implementation Requirements#
graph TD
A[Implementing Equality] --> B[Override equals method]
A --> C[Override hashCode method]
A --> D[Define equality criteria]
B --> E[Compare meaningful properties]
C --> F[Generate consistent hash codes]
D --> G[Decide what makes objects equal]
style A fill:#fff3e0
style B fill:#e8f5e8
style C fill:#e8f5e8
Hash Collections Contract#
Hash-based collections like HashMap and HashSet use hash codes to quickly locate and store objects. For these collections to work correctly, there's a critical contract: if two objects are equal according to equals(), they must produce the same hash code.
This contract ensures that:
- Equal objects are stored in the same hash bucket
- Collections can efficiently find and retrieve objects
- Duplicate objects are properly identified and handled
graph LR
A[Hash Collections] --> B[HashMap, HashSet]
B --> C[Requires consistent equals + hashCode]
C --> D[Equal objects must have same hash]
style A fill:#f3e5f5
style C fill:#ffebee
The programmer defines what "equal" means for their objects. This decision affects how objects behave in collections and hash-based data structures. When two objects are equal, they must produce the same hash code to work correctly with hash collections.
Let's explore how different programming languages implement equality:
Override equals() and hashCode() methods. Equal objects must have the same hash code for hash collections.
public class Guitar {
private String brand;
private String model;
public Guitar(String brand, String model) {
this.brand = brand;
this.model = model;
}
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Guitar guitar = (Guitar) obj;
return Objects.equals(brand, guitar.brand) &&
Objects.equals(model, guitar.model);
}
@Override
public int hashCode() {
return Objects.hash(brand, model);
}
}
// Create instances of Guitar
Guitar guitar1 = new Guitar("Fender", "Stratocaster");
Guitar guitar2 = new Guitar("Fender", "Stratocaster");
Guitar guitar3 = new Guitar("Gibson", "Les Paul");
System.out.println(guitar1.equals(guitar2)); // Output: true
System.out.println(guitar1.equals(guitar3)); // Output: false
Override equals() and hashCode() functions. Uses == operator for value equality.
class Guitar(val brand: String, val model: String) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Guitar
return brand == other.brand &&
model == other.model
}
override fun hashCode(): Int {
return brand.hashCode() * 31 + model.hashCode()
}
}
// Create instances of Guitar
val guitar1 = Guitar("Fender", "Stratocaster")
val guitar2 = Guitar("Fender", "Stratocaster")
val guitar3 = Guitar("Gibson", "Les Paul")
println(guitar1 == guitar2) // Output: true
println(guitar1 == guitar3) // Output: false
Implement custom equals() method to compare object properties.
class Guitar {
brand: string;
model: string;
constructor(brand: string, model: string) {
this.brand = brand;
this.model = model;
}
equals(other: any): boolean {
return this === other || (
other instanceof Guitar &&
this.brand === other.brand &&
this.model === other.model
);
}
}
// Create instances of Guitar
const guitar1 = new Guitar("Fender", "Stratocaster");
const guitar2 = new Guitar("Fender", "Stratocaster");
const guitar3 = new Guitar("Gibson", "Les Paul");
console.log(guitar1.equals(guitar2)); // Output: true
console.log(guitar1.equals(guitar3)); // Output: false
Override the == operator and hashCode getter for equality comparison.
class Guitar {
String brand;
String model;
Guitar(this.brand, this.model);
@override
bool operator ==(Object other) =>
identical(this, other) ||
other is Guitar &&
runtimeType == other.runtimeType &&
brand == other.brand &&
model == other.model;
@override
int get hashCode => brand.hashCode ^ model.hashCode;
}
// Create instances of Guitar
Guitar guitar1 = Guitar("Fender", "Stratocaster");
Guitar guitar2 = Guitar("Fender", "Stratocaster");
Guitar guitar3 = Guitar("Gibson", "Les Paul");
print(guitar1 == guitar2); // Output: true
print(guitar1 == guitar3); // Output: false
Conform to Equatable protocol and implement the == function.
class Guitar: Equatable {
var brand: String
var model: String
init(brand: String, model: String) {
self.brand = brand
self.model = model
}
static func ==(lhs: Guitar, rhs: Guitar) -> Bool {
return lhs.brand == rhs.brand && lhs.model == rhs.model
}
}
// Create instances of Guitar
let guitar1 = Guitar(brand: "Fender", model: "Stratocaster")
let guitar2 = Guitar(brand: "Fender", model: "Stratocaster")
let guitar3 = Guitar(brand: "Gibson", model: "Les Paul")
print(guitar1 == guitar2) // Output: true
print(guitar1 == guitar3) // Output: false
Override the __eq__ method to define equality comparison.
class Guitar:
def __init__(self, brand, model):
self.brand = brand
self.model = model
def __eq__(self, other):
if not isinstance(other, Guitar):
return False
return self.brand == other.brand and self.model == other.model
# Create instances of Guitar
guitar1 = Guitar("Fender", "Stratocaster")
guitar2 = Guitar("Fender", "Stratocaster")
guitar3 = Guitar("Gibson", "Les Paul")
print(guitar1 == guitar2) # Output: true
print(guitar1 == guitar3) # Output: false