객체의 해시코드
객체를 식별할 하나의 정수값을 말한다.
Object의 hashCode()메소드는 객체의 메모리번지(주소)를 이용해서 해시코드(int)를 만들어 리턴
*따라서 개별객체는 해시코드가 모두다르다.
논리적동등비교시 hashCode() 오버라이딩의 필요성
컬렉션 프레임워크의 HashSet, HashMap, Hashtable과 같은 (컬렉션프레임워크) 클래스는 두객체가 동등한
객체인지 판단할 때 아래와 같은 과정을 거침
따라서 논리적 동등 객체는 hashCode()가 리턴하는 값도 같아야됨
(논리적으로 동등하다는것 = 같은객체이건 다른 객체이건 상관없이 객체가 저장하고 있는 데이터가 동일함을 뜻)
equals()를 오버라이딩하면 hashCode()도 오버라이딩해야한다.
equals()결과가 true인 두객체의 해시코드는 같아야 하기때문!
예제1 ↓
public class Key {
public int number;
public Key(int number){
this.number = number;
}
@Override
public boolean equals(Object obj) {
System.out.println("equals()");
if(obj instanceof Key){
Key compareKey = (Key)obj;
if(this.number == compareKey.number){
return true;
}
}
return false;
}
@Override
public int hashCode() {
//number가 동일한 key객체는 다 똑같은 해시코드가 리턴된다.
System.out.println("HashCode()");
return number;
}
}
import java.util.HashMap;
public class KeyExam {
public static void main(String[] args) {
//키가 같다면 키값이 같으면 키에 저장되어있는 String을 주게 만들것이다.
HashMap<Key, String>hashMap = new HashMap<Key, String>();
hashMap.put(new Key(1),"홍길동");
//찾아오기
String value = hashMap.get(new Key(1));
System.out.println(value);
}
}
완전하게 논리적으로 동등하게 만들려면 > equals() 와 hashcode()도 재정의하여 만들어주어야 한다.
예제2↓
package sec03.exam02_hashcode;
public class Key {
public int number;
public int enc;
public Key(int number) {
this.number = number;
this.enc = 2300;
}
@Override
public boolean equals(Object obj) {
if(obj instanceof Key) {
//compareKey는 obj의 Key타입 형변환 내용을 대입받는다.
Key compareKey = (Key) obj;
if(this.number == compareKey.number) {
return true;
}
}
return false;
}
@Override
public int hashCode() {
return number+enc;
}
}
package sec03.exam02_hashcode;
import java.util.HashMap;
public class KeyExample {
public static void main(String[] args) {
HashMap<Key, String> hashMap = new HashMap<Key, String>();
Key testkey = new Key(23);
Key testkey_1 = new Key(23);
System.out.println(testkey.hashCode());
System.out.println(testkey_1.hashCode());
System.out.println("---------------------------------------");
String value = hashMap.get(new Key(1));
System.out.println(value);
System.out.println(hashMap.get(testkey).hashCode());
}
}
아래는 주석으로 설명 단 코드 (위 예제2과 동일)
package sec03.exam02_hashcode;
//Key라는 클래스(껍데기, 설계도) *heap메모리에 있는거 아닙니다.
public class Key {
public int number;
public int enc;
//생성자(int number를 매개변수로 받음)
public Key(int number) {
this.number = number;
this.enc = 2300;
}
/*메서드 : 이 인스턴스가 가지고있는 기능!
메서드 eauals는 Object에 있는 equals 매서드를 재정의해서 사용하겠다!
모든클래스는 Object라는 클래스에서 시작되며, 상속을 받습니다.
equals를 재정의해서 새로운 비교구문을 스스로 만들기
@ : 어노테이션은 아래메서드의 성질을 JVM에게 선언해주는 구문 (JVM이 Key클래스에 있는 equals라는 메서드가
Object에서 상속받은 메서드가 아닌 재정의된 메서드인것으로 인식할 수 있도록 @어노테이션 Override라서 선언)
boolean : 이 메서드가 실행되고 최종적으로 반환 (리턴)되는 "타입"이 boolean(true/fasle 둘중하나반환)
(Object obj) : 매개변수로서 어떤 인스턴스든 Object로 형변환 된다
모든객체는(메서드)는 입구(통로)는 매개변수이기 때문에
Object로 "자동형변환" (Object obj)>>하면 모든 클래스의 부모는 Object이기 때문에 가능
모든 클래스를 매개변수로 받을 수 있으므로 자바의 다형성을 표현한다.
어디까지나 타입의 형변환입니다 (인스턴스의 타입이 바뀌는 게 아님
heap 메모리에 있는 인스턴스가 바뀌는 것이 아님)*/
@Override
public boolean equals(Object obj) {
if(obj instanceof Key) {
//compareKey는 obj의 Key타입 형변환 내용을 대입받는다.
Key compareKey = (Key) obj;
if(this.number == compareKey.number) {
return true;
}
}
return false;
}
/* 주소+31(홀수 > 특정연산작업속도를간략히)+기타등등(컴퓨터의 기초메타데이터)
hashcode()메서드는 equals()메서드랑 동일하게 Object 클래스(최상의 클래스)의 메서드
어떤클래스나 Object 클래스를 상속받기 때문에 hashcode()와 equals() 메서드를 쓸 수 있다*/
@Override
public int hashCode() {
return number+enc;
}
}
package sec03.exam02_hashcode;
import java.util.HashMap;
public class KeyExample {
/* static : 프로그램이 실행되는 시점에서 1개만 만들어진 인스턴스
(String[] args) : 매개변수를 String 배열로 받겠다*/
public static void main(String[] args) {
/*Key객체를 식별키로 사용해서 String 값을 저장하는 HashMap객체생성
HashMap이라는 타입으로 선언하되 안에 값은 Key라는 객체와 String 문자열을 넣는다
= new HashMap<Key, String>(); : Heap메모리에 인스턴스만들기
HashMap<Key, String> Key위치는 String, Value위치는 String
key(String)으로 검색해서 Value(String)을 받는다.HashMap의 구조
HashMap 특징 : Key값은 중복안됨
Key값을 "김준석" Value "오전수업"
Key "김준석" Value "오후수업"
"김준석"을 검색하면 "오후수업"이 뜹니다.*/
HashMap<Key, String> hashMap = new HashMap<Key, String>();
//Key라는 객체타입으로 testkey라는 변수명으로 타입선언
//=new key(23); = heap메모리에서는 Key라는 인스턴스를 생성(단, 인자값 23넣음)
//생성자(매개변수23인)실행
Key testkey = new Key(23);
Key testkey_1 = new Key(23);
System.out.println(testkey.hashCode());
System.out.println(testkey_1.hashCode());
System.out.println("---------------------------------------");
/*json 포맷으로 인터넷 통신으로 주로합니다 (Key, value)
식별기 "new Key(1)"로 "홍길동"을 저장함
hashMap에는 put() : hashmap에 데이터넣기
String[] a; a[0] = "김준석"
"김준석" = "홍길동"
Key값이 중복이 안돼요 */
hashMap.put(new Key(1), "홍길동");
hashMap.put(testkey, "김준석");
hashMap.put(testkey, "오준석");
/*식별키 "new Key(1)"로 "홍길동"을 저장함
hashMap에는 put() : hashmap에 데이터 넣기*/
/*식별키 "new Key(1)"로 "홍길동"을 읽어옴
hashmap.get() : 인자값(key)를 넣어서 key와 함께넣은 value를 반환(출력)
hashmap.get() 메서드의 비교방식
new key(1)이라고 get()에 매개변수를 넣으면
새로운 인스턴스가 만들어져요 !
홍길동의 key값과 다르다고 인식(인스턴스가 다르고, 주소가다르므로 hashcode()도 다르기 때문)
홍길동의 key와 다르다고 인식 (hashcode()기반으로 구분 = HashMap의 자료저장 구조)★!!*/
String value = hashMap.get(new Key(1));
System.out.println(value);
System.out.println(hashMap.get(testkey).hashCode());
}
}
홍길동의 key값과 다르다고 인식(인스턴스가 다르고, 주소가다르므로 hashcode()도 다르기 때문)
홍길동의 key와 다르다고 인식 (hashcode()기반으로 구분 = HashMap의 자료저장 구조)★
equals와 hashCode 같이 재정의해야 하는 이유
원본 링크 : equals와 hashCode는 왜 같이 재정의해야 할까? (techcourse.co.kr)
public class Car {
private final String name;
public Car(String name) {
this.name = name;
}
// intellij Generate 기능 사용
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Objects.equals(name, car.name);
}
}
Car 클래스에는 equals만 재정의해두었다.
public static void main(String[] args){
Car car1 = new Car("foo");
Car car2 = new Car("foo");
// true 출력
System.out.println(car1.equals(car2));
}
equals를 재정의했기 때문에 Car 객체의 name이 같은 car1, car2 객체는 논리적으로 같은 객채로 판단된다.
이제 아래 main 메서드의 출력 결과를 예측해보자.
public static void main(String[] args) {
List<Car> cars = new ArrayList<>();
cars.add(new Car("foo"));
cars.add(new Car("foo"));
System.out.println(cars.size());
}
Car 객체를 2개 List<Car> cars에 넣어줬으니 출력 결과는 당연히 2 일 것이다.
그렇다면 이번엔 Collection에 중복되지 않는 Car 객체만 넣으라는 요구사항이 추가되었다고 가정해보자.
요구사항을 반영하기 위해 List에서 중복 값을 허용하지 않는 Set으로 로직을 바꿨다.
마찬가지로 아래 main 메서드의 출력 결과를 예측해보자.
ublic static void main(String[] args) {
Set<Car> cars = new HashSet<>();
cars.add(new Car("foo"));
cars.add(new Car("foo"));
System.out.println(cars.size());
}
추가된 두 Car 객체의 이름이 같아서 논리적으로 같은 객체라 판단하고 HashSet의 size가 1이 나올 거라 예상했지만,
예상과 다르게 2가 출력된다.
hashCode를 equals와 함께 재정의하지 않으면 코드가 예상과 다르게 작동하는 위와 같은 문제를 일으킨다. 정확히 말하면 hash 값을 사용하는 Collection(HashSet, HashMap, HashTable)을 사용할 때 문제가 발생한다.
왜 그럴까?
앞서 말한 hash 값을 사용하는 Collection(HashMap, HashSet, HashTable)은
객체가 논리적으로 같은지 비교할 때 아래 그림과 같은 과정을 거친다.
hashCode 메서드의 리턴 값이 우선 일치하고 equals 메서드의 리턴 값이 true여야 논리적으로 같은 객체라고 판단한다.
앞서 봤던 main 메서드의 HashSet에 Car 객체를 추가할 때도 위와 같은 과정으로 중복 여부를 판단하고
HashSet에 추가됐다. 다만 Car 클래스에는 hashCode 메서드가 재정의 되어있지 않아서
Object 클래스의 hashCode 메서드가 사용되었다.
Object 클래스의 hashCode 메서드는 객체의 고유한 주소 값을 int 값으로 변환하기 때문에 객체마다 다른 값을 리턴한다. 두 개의 Car 객체는 equals로 비교도 하기 전에 서로 다른 hashCode 메서드의 리턴 값으로 인해
다른 객체로 판단된 것이다.
hashCode 재정의
앞서 살펴봤던 문제를 해결하기 위해 Car 클래스에 hashCode 메서드를 재정의해보겠다.
public class Car {
private final String name;
public Car(String name) {
this.name = name;
}
// intellij Generate 기능 사용
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Car car = (Car) o;
return Objects.equals(name, car.name);
}
@Override
public int hashCode() {
return Objects.hash(name);
}
}
intellij 의 Generate 기능을 사용했더니 Objects.hash 메서드를 호출하는 로직으로 hashCode 메서드가 재정의 됐다. Objects.hash 메서드는 hashCode 메서드를 재정의하기 위해 간편히 사용할 수 있는 메서드이지만 속도가 느리다.
인자를 담기 위한 배열이 만들어지고 인자 중 기본 타입이 있다면 박싱과 언박싱도 거쳐야 하기 때문이다.
성능에 아주 민감하지 않은 대부분의 프로그램은 간편하게 Objects.hash 메서드를 사용해서
hashCode 메서드를 재정의해도 문제 없다.
무조건 같이 재정의해줘야 할까?
‘hash 값을 사용하는 Collection을 사용 하지 않는다면,
equals와 hashCode를 같이 재정의하지 않아도 되는건가?‘라고 생각할 수 있다.
무조건 같이 재정의하라고 말할 자신은 없다.
하지만 요구사항은 항상 변한다. hash 값을 사용하는 Collection을 사용할 리 없다는 자신의 판단은 틀렸을 가능성이 높다.
또, 협업 환경이라면 동료는 당연히 equals와 hashCode를 같이 재정의했을 거라고 생각하고 hash 값을 사용하는 Collection으로 수정할 가능성도 있다.
굳이 이런 위험한 코드를 안고 가지말고 equals와 hashCode는 항상 같이 재정의해주는 게 맞다고 생각한다.
이미지출처 : 이것이자바다
'STUDY > JAVA' 카테고리의 다른 글
[JAVA] 22-07-27 객체 문자정보(toString()) / 객체복제(clone()) (0) | 2022.07.27 |
---|---|
[JAVA-이것이자바다.10장] 예외 확인 문제 (0) | 2022.07.24 |
[JAVA-이것이자바다.9장] 중첩 클래스와 중첩 인터페이스 확인 문제 (0) | 2022.07.20 |
[JAVA] 22-07-20 자바 API / Object 클래스 ☑ (0) | 2022.07.20 |
[JAVA] 22-07-20 객체비교 equals() ☑ (0) | 2022.07.20 |