Java의 equals()와 hashCode() 사용법

Java java 개발중 특히 VO 클래스를 만들때 getter(), setter(), toString(), equals(), hashCode() 함수등을 자주 재정의 해서 사용한다.

getter(), setter(), toString()은 그 자체로 의미가 명확하지만 **equals(), hashCode()**는 왜 재정의 하여 사용하는지, 왜 두 메소드는 항상 함께 사용하는지 모호하여 다시 공부하여 정리를 해보도록 하겠다.

미리 간단히 말한다면 equals()와 hashcode()를 재정의 하여 객체가 논리적으로 일치하는지 확인하는 용도로 사용된다

Object.equals() 란?

기본형태는 아래와 같다.

1
public boolean equals(Object obj){ ... }

Object 클래스의 equals() 메소드는 비교 연산자인 ==과 동일한 결과를 리턴한다. 즉 객체의 값의 일치여부를 비교하여 true, false를 리턴한다.

1
2
3
4
5
6
7
8
9
Object obj1 = new Object();
Object obj2 = new Object();

boolean result1 = obj1.equals(obj2);

boolean result2 = (obj1 == obj2);

System.out.println("equals결과 :"+result1); // 결과 false
System.out.println("==연산결과 :"+result2); // 결과 false

변수 obj1, obj2는 각각의 인스턴스의 메모리 주소값을 가지게 되며 equals()메소드와 ==연산자와 마찬가지로 그 값들이 일치 여부를 비교하게 되며, 일치한다면 true 불일치한다면 false를 반환하게 된다.

이번에는 String의 equlas메소드를 확인해보자

1
2
3
4
5
6
7
8
9
//새로운 문자열 객체 생성, new 연산자로 강제로 새로운 메모리 주소를 할당하게 했다.
String name3 = new String("가나다라");
String name4 = new String("가나다라");

boolean result5 = name1.equals(name3);
boolean result6 = (name1 == name4);

System.out.println("equals결과 :"+result5); // 결과 true
System.out.println("==연산결과 :"+result6); // 결과 false

String 예제는 Object 예제와 다르게 equals메소드는 true, ==연산자는 false를 리턴한다. String의 equals 메소드는 Object의 equals메소드를 그대로 쓰는 것이 아니라, 재정의 되어 논리적인 문자열 비교를 하기 때문이다.

따라서 사용자가 String 클래스처럼 equlas를 논리적 일치여부를 검사하는 부분을 재정의 하여 사용하지 않는 이상, 객체의 메모리 값만 비교하게 된다.

Object.equals() 재정의 하기 feat.instancOF 메소드

바로 예제로 가보자. 문자열 id 하나만 프로퍼티로 갖는 간단한 VO 클래스이다. 앞서 말했듯이 MemberVO의 논리적 일치여부를 equals메소드에 재정의 하였다. id가 일치하면 MemverVO는 동일한 객체로 여기기로 하였다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class MemberVO{
public String id;

@Override
public boolean equals(Object obj) {
if(obj instanceof MemberVO){
MemberVO memberVO = (MemberVO) obj;
if(id.equals(memberVO.id)){
return true;
}//_if
}//_if

return false;
}//_equals
}

//어딘가의 main메소드 에서
MemberVO vo1 = new MemberVO();
MemberVO vo2 = new MemberVO();
vo1.id ="가나다";
vo2.id ="가나다";

System.out.println(vo1.equals(vo2)); // 결과 true

간단한 소스이다. 주의할 점은 instancOf메소드를 통해 equals메소드의 인자객체와 메소드를 호출주체 객체와 동일 타입인지를 확인해야 한다. 만약 id 프로퍼티를 갖는 다른 타입의 객체와 비교시 불일치 여부를 판단 할수 있어야 하기 때문이다.

자 대충 equlas메소드를 재정의 하여 객체의 논리적 일치여부를 확인하는 예제를 만들어 봤다. 하지만 equals메소드 하나만 가지고는 부족하다. HashSet, HashMap, Hashtable의 컬랙션프레임워크의 객체 동등 비교시 hashCode()메소드를 실행하기 때문이다.

Object.hashCode() 란?

Object.hashCode() 메소드는 객체의 주소값을 이용하여 객체 고유의 해시코드를 리턴하는 함수이다. 이 hashCode()를 equals()와 함께 오버라이딩 해야 하는 이유는 HashSet, HashMap, Hashtable과 같은 프레임워크에서 객체의 비교시 hashCode() 결과값을 통해 해시코드 값이 다르면 다른 객체로 판단하기 때문이다.

HashSet, HashMap, Hashtable 프레임워크가 두 객체의 동등 비교 프로세스

1
2
3
4
5
6
7
8
9
if : hashCode() == true {
if : equals() == true{
해쉬 프레임워크는 이제서야 두 객체를 동등하다고 판단한다.
}else{
다른객체로 판단
}
}else{
다른객체로 판단
}

즉 객체의 동등 비교를 위해서는 Object의 equals() 메소드만 재정의하지 말고 hashCode() 메소드도 재정의해서 논리적 동등 객체일 경우 동일한 해시 코드가 리턴 되도록 해야 한다.

따라서 아래처럼 hashCode를 오버라이딩 해서 MemverVO의 id가 동일한 문자열인 경우 같은 해시 코드를 리턴하게 해야 한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class MemberVO{
public String id;

@Override
public boolean equals(Object obj) {
if(obj instanceof MemberVO){
MemberVO memberVO = (MemberVO) obj;
if(id.equals(memberVO.id)){
return true;
}//_if
}//_if

return false;
}//_equals

@Override
public int hashCode(){
return id.hasnCode(); //id가 동일한 문자열인 경우 같은 해시 코드를 리턴
}//_hashCode
}