equals 와 hashCode (자바)

Flash·2022년 12월 3일
0

알고리즘 문제 하나를 풀다가 HashSet 을 이용했는데 평소랑 다른 결과에 당황했다. 한참 뒤에 뭐가 문제인지 알게 되고, 자바에 대한 기초 상식이 참 부족하다는 걸 깊이 느꼈다. 그동안은 알고리즘 풀이 글만 작성했는데 자바 자체에 관한 공부도 기록으로 남겨야겠다는 생각을 했다.

객체의 Identity

String s1 = new String("string");
String s2 = new String("string");

boolean flag = s1.equals(s2); // true
boolean flag1 = (s1==s2); // false

두 참 거짓 결과가 서로 다른 이유는 자바에서 Object 타입은 equals 라는 메소드를 정의해두어 제공하기 때문이다. 아래 flag1== 연산자는 주솟값이 같은지 확인하기 때문에 당연히 서로 다른 두 객체에 대해 false 를 반환하게 되는 것이다.
하지만 .equals(Object o) 메소드는 위의 경우에, String 안에 정의되어 있는 equals()를 통해 동일한 내용을 갖고 있는지를 판별해준다. 실제로 cmd+B 를 눌러 확인해보면 다음과 같이 길이부터 같은지 판별한 후에 한 글자 한 글자씩 확인하는 메소드임을 확인할 수 있다.


HashSet 은 어떻게 객체들을 구별할까?

내가 결국 이번 포스트를 작성하게 된 이유는 HashSet 을 잘 모르고 썼기 때문이다.
그저 primitive 타입들이나 String 과 같이 자바에서 기본적으로 제공하는 객체들에 대해서는 문제가 없이 사용해왔고 여지껏 그런 경우에만 사용해왔다.

하지만 새롭게 만났던 알고리즘 문제에서는 내가 직접 정의한 객체 타입을 HashSet 에 집어넣었는데 아무리 반복해도 같은 내용을 가진 객체들을 반복해서 집어넣는 결과를 마주했다.

이는 CollectionHashMap, HashSet 등을 이용할 때 element 들의 고유성을 판별하기 위해 다음과 같은 과정을 거친다.

  1. 두 객체의 hashCode 리턴값을 비교한다. 다르면 서로 다른 객체. 같으면 2로 넘어감
  2. 두 객체의 equals 리턴값을 비교한다. 다르면 서로 다른 객체. 같으면 동일 객체!

위 두 과정을 통해 hashCode 값과 equals 값을 && 로 판단한다는 것!

따라서 이 둘의 리턴값이 어떻게 생성되는지를 살펴봐야 한다.


hashCode

각 객체의 주솟값을 Integer 형태로 hash 값에 넘겨준다고 한다. 따라서 모든 객체가 고유한 주솟값을 가지기에 모두가 서로 다른 hashCode 값을 갖게 될 것이다. 이를 통해 Collection 의 자료구조들이 고유성을 구별한다고 한다. 따라서 내가 새롭게 정의한 클래스로부터 생성될 객체들을 구별할 때에 내용만 같다면 같은 객체로 판단하고 싶다면... 또한 이 객체들을 HashSet 같은 자료구조에 넣어주고 싶다면... hashCode 를 반드시 override 해야한다.

예를 들면 다음과 같다.

좌표를 저장해줄 Pos 클래스를 정의했다.

하지만 아직 두 Pos 객체에 대해 동일한지 판단하기 위한 모든 작업이 끝난 것이 아니다.


equals

앞서 언급했듯이 먼저 hashCode 값을 비교한 후에 equals 값을 비교한다고 했다. 그렇다면 새롭게 정의한 클래스에 equals 메소드도 오버라이딩 해주어야 한다.
다음과 같다.

이제 서로 다른 두 Pos 객체의 동일성 여부를 판단할 수 있는 준비가 모두 끝났다!


확인해보기

hashCode 메소드를 확인할 때 의문이 하나 들 수 있었다. 그냥 x+y 값만으로 hash 값을 반환해주면 (1,1) 이나 (2,0) 이나 같다고 판단하지 않을까?

실제로 같다고 판단하는 것을 위 캡쳐 화면을 통해 볼 수 있다.
하지만 그렇다고 객체 자체가 동일하다고까지 판단하지는 않는 것을 볼 수 있다. 왜냐하면 다음 캡쳐 화면에서 볼 수 있듯이 HashSet 에서 객체들을 추가할 때 hashCode 뿐만 아니라 equals 까지 따지기 때문에 결국 p1 과 p3 만 들어가는 것을 확인할 수 있다.


정리

새로운 클래스를 정의하고 그 객체들을 다룰 때 동일성을 판단해야할 일이 생긴다면 반드시!!

1. equals
2. hashCode

를 모두 오버라이딩 해주어야 한다는 점. 잊지 말자!

더 깊이

만약 hashCode 값이 객체의 주솟값을 사용한다고 하면... 어째서 서로 다른 두 String 을 같은 내용으로 생성했을 때 둘의 hashCode 값을 비교했을 때 같다고 나오는 거지...?

s1s2 의 주솟값을 비교하는 연산자 == 의 결과로는 false 가 나오면서, 어째서 주솟값을 hash 값으로 쓴다는 hashCode()의 값 비교는 true 가 나오는 거지???? 띠용

공식 문서를 읽어보면 결국 서로 다른 객체는 서로 다른 hashCode 값을 준다면서요...

StringhashCode 는 뭔가 다른 건가... 해서 String 에서의 hashCodecmd+B 해가며 찾아본 결과... 아래와 같다.

필요한 부분만 보자면 다음과 같은 것이다.

String 은 자바에서 애초에 hashCode 메소드를 이미 오버라이딩 해둔 것이다... 오호라...

마치 앞서 직접 정의한 Pos 클래스에서 hashCode 메소드를 재정의한 것처럼 말이다!


참고 자료

  1. https://tecoble.techcourse.co.kr/post/2020-07-29-equals-and-hashCode/
  2. https://nowonbun.tistory.com/292
profile
개발 빼고 다 하는 개발자

0개의 댓글