What's equals()?
In default code in Object.java, equals() is defined as follows:
public boolean equals(Object obj) {
return (this == obj);
}
What's hashcode()?
In the same Object.java class, hashcode() is defined as a native function.
public native int hashcode();
Returns a hash code value for the object. This method is supported for the benefit of hash tables such as those provided by java.util.HashMap.
As we can sse, the purpose of hashcode() is to be used in the hash-related classes including HashMap, HashSet, HashTable, and ConcurrentHashMap.
Why do we need both equals() and hashcode() ?
As discussed above, the purpose to have hashcode() method is for operations in the hash-related classes in java. If you have read my last article about hashmap.
There's a segement of code like this:
해시맵에서 키 값의 존재 유무나 충돌 여부를 확인하는 로직은 다음과 같다.
node.hash == hash && (node.key == key || key != null && key.equals(node.key))
HashMap 에서
hashcode 랑 key 의 차이는 무엇인가?
HashCode
Decide where to store in hashmap (real memory map)
HashMap 에서는 새 쌍을 추가할 때 해시코드가 같은 값이 존재한다고 무조건 같은 값 처리하진 않고 다음으로 Key 를 검사함.
HashCode 가 다른 경우 무적권 값이 존재하지 않음
📝 값의 존재 유무, 동일성 검사를 빠르게 할 수 있어 성능이 뛰어남.
Key
HashMap 안에 logical key 역할, hash 값 충돌시 다음으로 검사해봄.
데이터의 신뢰성을 보장하는 역할
Case study: What if you only override equals()?
We'll make a Order class with equation operating with orderId.
public class Order {
private int orderId;
private String item;
public Order(int orderId, String item) {
this.orderId = orderId;
this.item = item;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Order order = (Order) o;
return orderId == order.orderId;
}
}
same order Id but..
public void test(){
HashSet<Order> orders = new HashSet<>();
Order orderA = new Order(1, "bread");
Order orderB = new Order(1, "butter");
orders.add(orderA);
orders.add(orderB);
System.out.println(orderA.equals(orderB)); // true
System.out.println(orders.size()); // 2 , What's going on?
}
In HashSet, a HashMap is used to record the objects added to the set, with the key as the passed object and the value a s a dummy new Object() shown by the source code:
private transient HashMap<E, Object> map;
// Dummy value to associate with an Object in the backing Map
private static final Object PRESENT = new Object();
public boolean add(E e) {
return map.put(e, PRESENT) == null;
}
// 해시코드는 다른데, equals 만 만족하는 경우임 지금이.
Until, here, hashcode() in Order is not overriden yet. HashMap comparison mechanism still takes two orders as different references, producing different has values, even though they are defined as equal by our code.
This violates the rule we mentioned earlier: if two objects are equal, their hashcode has to be equal.
Always override hashcode() when you do equals()
If you don't, HashMap append data since there's no hash collision without checking key of map
Note we will override hashcode() method in Order class, in which we will take `orderId` as the input to caculate the hash.
class Champion(val id: Int, var name: String, var age: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this.javaClass != other.javaClass) return false
return this.id == (other as Champion).id
}
}
@Test
internal fun testHashSet() {
val zix = Champion(1, "Zix", 22)
val hecarim = Champion(2, "Hecarim", 100)
val narr = Champion(3, "Narr", 15)
val garen = Champion(4, "Garen", 30)
val akalli = Champion(4, "Akalli", 25) // garen 과 같은 id
val championSet = hashSetOf<Champion>(zix, hecarim, narr, garen, akalli)
println(championSet.size) // 5
assertEquals(true, garen.equals(akalli)) // 같은 id 를 가졌음에도 집합에 추가됨.
assertEquals(false, garen.hashCode() == akalli.hashCode()) // 해시코드가 다르기 때문에 중복 허용됨.
for (champion in championSet) {
print("${champion.name} ")
}
// [Print]
// Hecarim Akalli Zix Garen Narr
}
class Champion(val id: Int, var name: String, var age: Int) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || this.javaClass != other.javaClass) return false
return this.id == (other as Champion).id
}
// Hash 관련 컬렉션은 hashCode 를 통해 동일성 검사를 하고
// HashSet 일 경우엔 equals 의 동등성 검사까지 둘다한다.
// (동등성 && 동등성 모두 만족시 동일 오브젝트로 간주한다.)
override fun hashCode(): Int {
return Objects.hashCode(this.id)
}
}
@Test
internal fun testHashSetWithHashCode() {
val zix = Champion(1, "Zix", 22)
val hecarim = Champion(2, "Hecarim", 100)
val narr = Champion(3, "Narr", 15)
val garen = Champion(4, "Garen", 30)
val akalli = Champion(4, "Akalli", 25) // garen 과 같은 id
val championSet = hashSetOf<Champion>(zix, hecarim, narr, garen, akalli)
// 4 (akalli 삽입이 무시됨)
println(championSet.size)
// ✅ 동등성 검증
assertEquals(true, garen.equals(akalli))
// ✅ 해시코드 (id) 가 같으므로 동일함 => 중복 발생
assertEquals(true, garen.hashCode() == akalli.hashCode())
for (champion in championSet) {
print("${champion.name} ")
}
// [Print]
// Zix Hecarim Narr Garen
}
}
🔗 Reference
'JVM > Kotlin' 카테고리의 다른 글
[Kotlin] Array vs ArrayList vs LinkedList vs Queue (0) | 2022.09.18 |
---|---|
[Kotlin] data class (0) | 2022.09.18 |
[Kotlin] HashMap, HashSet, LinkedHashSet (0) | 2022.09.17 |
[Kotlin] SortedSet, NavigableSet, TreeSet (0) | 2022.09.16 |
[Kotlin] Comparator, Comparable (0) | 2022.09.16 |