느린 것을 걱정하지 말고, 멈춰서는 것을 걱정하라
article thumbnail
회사업무중 중국개발자 분들(위메프 연길센터에서 업무를 수행하시는 분들)이 작성한 통계관련 코드에 기능을 추가하는 페어프로그래밍을 하던도중, 이슈가 있는 코드를 발견하였다. Custom하게 만든 객체를 HashMap의 키값으로 사용하였고, Custom객체를 통해 key값에 해당하는 값을 갖고오는데 원하는 데로 갖고오지 않았기 때문이다.
 
문제가 있었던 부분은 Custom객체를 확인하자 바로 알 수 있었다. 문제가 있었던 부분은 Effective Java에서 강조하는 아래의 법칙을 지키지 않았기 때문이다. 
아래의 글은 Effective 자바의 글이다. 
 
equals를 재정의할 떄는 반드시 hashCode도 재정의 하라
 
많은 버그가 hashCode 메서드를 재정의하지 않아서 생긴다. equals 메서드를 재정의하는 클래스는 반드시 hashCode 메서드도 재정의 해야 한다. 그러지 않으면 Object.hashCode의 일반 규약을 어기게 되므로, HashMap, HashSet, Hashtable 같은 해시 기반 컬렉션과 함께 사용하면 오동작하게 된다. Object 클래스 명세에서 복사해 온 일반 규약을 아래에 보였다.
 
  • 응용프로그램 실행 중에 같은 객체의 hashCode를 여러 번 호출하는 경우, equals가 사용하는 정보들이 변경되지 않았다면, 언제나 동일한 정수가 반환되어야 한다. 다만 프로그램이 종료되었다가 다시 실행되어도 같은 값이 나올 필요는 없다.
  • equals(Object) 메서드가 같다고 판정한 두 객체의 hashCode 값은 같아야 한다. 
  • equals(Object) 메서드가 같다고 판정한 두 객체의 hashCode 값은 꼭 다를 필요는 없다.그러나 서로 다른 hashCode값이 나오면 해시 테이블의 성능이 향상될 수 있다는 점은 이해고 있어야 한다. 
 
hashCode를 재정의하지 않으면 위반되는 핵심 규약은 두 번째다. 같은 객체는 같은 해시 코드 값을 가져야 한다는 규약이 위반되는 것이다. equals 메서드가 논리적으로 같다고 판단한 두 객체라 해도 Object의 hashCode 입장에서 보면 그다지 공통점이 없는 두 객체일 뿐이다. 
따라서 Object의 hashCode 메서드는 규약대로 같은 정수를 반환하는 대신, 무작위로 선택된 것처럼 보이는 두 개의 정수를 반환한다. 
 
hashCode 메서드를 재정의하지 않으면 서로 다른 해시코드를 갖는다. hashCode 규약을 위반한 것이다. 따라서 get 메서드는 put 메서드가 객체를 저장한 것과 다른 해시 버킷을 뒤지게 된다. 설사 운이 좋아서 같은 해시 버킷을 뒤지게 되더라도 get 메서드는 거의 항상 null을 반환할 것이다. HashMap은 성능 최적화를 위해 내부에 보고나된 항목의 해시 코드를 캐시해 두고, 캐시된 해시코드가 없는 객체는 동일성 검사조차 하지 않기 때문이다. 
 
( 여기서 잠깐! 회사에서 우리가 고친 코드는 equals, hashcode 둘다 오버라이딩 하지 않았다. @EqualsAndHashCode 어노테이션을 이용해서 equals, hashcode를 구현하였는데, 주말간 궁금하여 hashCode만 재정의 하여 HashMap에 넣었는데도 원하는 결과가 나오지 않았다. 즉, hashCode를 Key로 사용하지 않는 것이다. 그러면, equals를 재정의하면 원하는 대로 동작하나 봤는데도, 원하는대로 동작하지 않았다.. 그렇다면,, 어떤 메커니즘을 통해 값을 갖고오는가.. 하였는데, 아래의 글이 궁금증을 해소해 줬다.  )

 

 

이 문제를 수정하는 간단한 방법은 적절한 hashCode 메서드를 구현하는 것이다. 
// 가장 끔찍한 형태의 해시 함수. 절대로 이렇게 구현하지 말 것.
@Override public int hashCode() 
{
    return 42;
}
 
모든 객체가 같은 해시코드를 가지게 되니 끔찍하다. 전부 같은 버킷에 해시되므로, 해시 테이블은 결국 연결리스트가 되어버린다. 실행시간이 선형적이어야 하는 프로그램들의 복잡도가 제곱에 비례하게 바뀌면서 끔찍하게 느려진다. 해시 테이블에 저장되는 자료가 많을 경우에는 결과적으로 프로그램은 동작하지 않는 것처럼 되어버린다.

 

좋은 해시 함수는 다른 객체에는 다른 해시 코드를 반환하는 경향이 있다. hashCode의 일반 규약 세 번쨰 항목에서 언급한 사항이 바로 이것이다. 이상적인 해시 함수는 서로 다른 객체들을 모든 가능한 해시 값에 균등하게 배분해야 한다. 그러나 이런 이상적 해시 함수를 만드는 것은 어려운 일이다.  다행인 것은 이상적인 해시 함수에 '가까운' 함수를 만드는게 어렵지 않다는 것이다. 
 

 

profile

느린 것을 걱정하지 말고, 멈춰서는 것을 걱정하라

@주현태

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!