이전 포스팅(DTO와 VO 그리고 Entity의 차이)에서 살짝 언급했는데, 이제서야 정리를 하게되었다.
equals()
와 hashCode()
가 무엇이고, 그래서 이들을 언제 사용하는지에 대해 정리해보았다.
무엇인가
두 메소드 모두 Object의 메소드이다.
equals()
는 객체의 값의 일치여부(boolean)을 반환하는 타입이다.
hashCode()
는 객체의 주소값(int)을 이용하여 객체 고유의 해시코드를 리턴하는 함수이다.
어떻게 동작하는가
1 | class DevAndy { |
String형 데이터를 2개 선언했다. 그러나 String Constant Pool에 생성하지 않고, new
라는 키워드로 heap에 선언했다.
String 변수를 선언하면서 리터럴을 이용하는것과 new
키워드로 선언하는 것의 차이는 아래의 포스팅을 참고바란다.
DevAndy - String 변수를 선언하는 2가지 방식
리터럴로 선언한것과 달리 heap에 생성한 변수들을 ==
연산자와 equals()
로 비교를 해보았다.
1 | str1==str2 : false |
==
연산자의 결과는 false
이고, equals()
의 결과는 true
이다.
==
연산자는 리터럴 값을 비교하는데 new String()
과 new String()
은 서로 다른 주소값을 참조하는 객체이기 때문에 false를 반환한것이다.
반면 equals()
는 어떻게 두 변수가 같다고 판단한걸까?
인텔리제이에서 equals()
메소드를 클릭해서 내부를 열어보았다(?)
먼저 객체 리터럴을 비교해서 true
면 그대로 반환한다. 그러나 같지않다면 그 다음으로 String 타입인지 데이터 타입을 체크하여 이때부터 본격적으로 비교를 들어간다. 최종적으로 언어코드(Latin/UTF16)에 따라 equals()
결과를 반환한다.
그럼 여기서 사용한 equals()
는 어떻게 동작할까?
비교하는 대상의 객체들의 길이를 비교하고 길이가 다르다면 false
를 반환한다. 길이가 같다면, 여기서 문자열 길이만큼 각각의 문자형 리터럴을 직접 비교한다.
equals()
가 동작하는건 이제 알겠는데, hashcode()
는 언제 써야할까?
언제 써야할까
위의 예제코드는 String형 변수를 비교하는 경우였다. 그렇다면, 컬렉션 프레임워크를 사용하는 경우엔 어떨까?
1 | class DevAndy { |
1 | Card{name='Hyundai', number='0000-0000-0000'} |
코드를 천천히 살펴보도록 하자. 중첩 클래스를 이용하여 Card
라는 클래스를 선언했다. 그리고 makeSet()
에서는 HashSet을 이용하여 Card
객체를 삽입했는데, 이상한 결과가 출력된걸 알 수 있다.
Set은 중복을 허용하지 않는다. 따라서 똑같은 속성을 지닌 Card 객체를 HashSet에 삽입했다면, HashSet에선 중복되는 데이터 하나는 삭제하고, size()
로 1
을 반환했어야 했다.
그런데 size()
를 2
로 반환했다는건 toString()
을 통해서도 확인했지만, new
로 선언한 두 Card 객체를 컴파일러에서 서로 다르다고 판단했던것이다.
그럼 왜 다르다고 판단한건지 hashCode를 출력해서 확인해보자.
HashSet에서 Card를 꺼내서 출력하는 코드를 아래처럼 바꿔서 다시 main 메소드를 실행해보자.
1 | for (Card card : set) { |
출력결과는 아래와 같다.
1 | 2101440631 |
정말 달랐다!
hashCode가 달랐기 때문에 컴파일러에선 같은 속성을 지닌 Card
객체들을 다르게 본 것이다.
이건 개발자가 의도한대로 프로그래밍되었다고 볼 수 없다. 바로 이 때가 equals()
와 hashCode()
를 오버라이딩해야 하는 순간이다.
cmd + n을 입력해서 인텔리제이에서 자동으로 생성하는 equals()
와 hashCode()
를 생성했다.
중첩 클래스인 Card
의 코드만 살펴보도록 하자.
1 | class Card { |
다시 main 메소드를 실행해보니 다른 결과가 출력된다.
1 | Card{name='Hyundai', number='0000-0000-0000'} |
비로소 HashSet에서 똑같은 속성을 지닌 Card
객체를 중복으로 인식하여 똑같은 객체는 삭제하고, 하나의 Card
객체만 갖고있는걸 확인할 수 있다.
여기서 중요한건 equals()
만 오버라이딩해서는 안되고, hashCode()
까지 오버라이딩해야 정상적으로 동작한다는 것이다.