Java Object의 hashCode()와 equals() 메소드

  • Post author:
  • Post category:
  • Post comments:0 Comments
  • Post last modified:December 7, 2020

참고 :  http://www.javaworld.com/article/2074996/hashcode-and-equals-method-in-java-object—a-pragmatic-concept.html

java.lang.Object의 메소드인 hashCode()와 equals()는 보편적으로 사용되는 메소드는 아니지만 상황에 따라 오버라이드 되어 사용되기도 합니다. 오버라이드시 어떤 기능을 할 수 있는지 한번 알아보도록 하겠습니다.

hashCode()

Object에 포함된 기본 상태의 hashCode() 메소드는 객체에 대한 메모리 주소 매핑 정보를 기반으로 정수 값을 반환 합니다. Object 클래스의 소스를 보면 알 수 있겠지만 hashCode() 메소드는 다음과 같이 작성 되어 있습니다.

public native int hashCode();

hashCode() 메소드가 JVM상에 네이티브 코드로 직접 구현되어 있다는 것을 의미하며 메모리 주소를 특정한 지역 정보 형태로 제공한다는 것을 알 수 있습니다. 이런 특징이 있음에도 불구하고 이 메소드는 구현체에서 오버라이드 될 수 있습니다.

equals()

equals() 메소드는 두개의 객체가 동일한지 비교할 수 있는 특별한 메소드 입니다. 자바에는 동일함을 비교하는 방식이 두 종류가 있는데 하나는 “==” 연산자를 사용하는 것이며 다른 하나는 “equals()” 메소드 입니다. equals() 메소드의 경우 다음과 같이 작성 되어 있습니다.

public boolean equals(Object obj) {
return (this == obj);
}

오버라이드가 가능하다는 측면에서 둘의 차이를 좀더 생각해 보자면 객체의 비교에 있어 “.equals()”는 서로 같은 값을 갖는 관계를 이야기 하며 “==” 연산자의 경우 물리적으로 완전히 동일함을 이야기 한다고 볼 수 있을 것 입니다.

규약

hashCode()와 equals()는 결국 상호 연관관계에 있는 메소드로 둘은 동시에 오버라이드 되어야 하며 그렇지 않으면 API문서에 나와있는 메소드 규약을 위반하게 됩니다.

테스트

테스트를 위해 hashCode()와 equals()모두를 오버라이드한 클래스와 둘다 변경하지 않은 클래스를 작성 하였습니다.

package reference.value;

public class Robot {
private int productNumber;
private String modelName;

public Robot(int productNumber, String modelName) {
super();
this.productNumber = productNumber;
this.modelName = modelName;
}

public int getProductNumber() {
return productNumber;
}

public void setProductNumber(int productNumber) {
this.productNumber = productNumber;
}

public String getModelName() {
return modelName;
}

public void setModelName(String modelName) {
this.modelName = modelName;
}

}

package reference.value;

public class CustomRobot {
private int productNumber;
private String modelName;

public CustomRobot(int productNumber, String modelName) {
super();
this.productNumber = productNumber;
this.modelName = modelName;
}

public int getProductNumber() {
return productNumber;
}

public void setProductNumber(int productNumber) {
this.productNumber = productNumber;
}

public String getModelName() {
return modelName;
}

public void setModelName(String modelName) {
this.modelName = modelName;
}

@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + ((modelName == null) ? 0 : modelName.hashCode());
result = prime * result + productNumber;
return result;
}

@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
CustomRobot other = (CustomRobot) obj;
if (modelName == null) {
if (other.modelName != null)
return false;
} else if (!modelName.equals(other.modelName))
return false;
if (productNumber != other.productNumber)
return false;
return true;
}

}

이 둘의 차이를 알아보기 위해 다음과 같은 테스트 클래스를 작성하였습니다.

package reference.value;

import java.util.HashSet;

public class TestHashAndEqual {

public static void main(String[] args) throws Exception {
Robot r1 = new Robot(1999, "R2D2");
Robot r2 = new Robot(1999, "R2D2");
Robot r3 = r1;

// toString in Object
System.out.println("r1 is " + r1 + " , r2 is " + r2 );
System.out.println("r1 == r2 result : " + (r1 == r2 ? true : false));
System.out.println("r1 == r3 result : " + (r1 == r3 ? true : false));
System.out.println("Equals between r1 and r2 : " + r1.equals(r2));
System.out.println("HashCode r1 : " + r1.hashCode() + " , r2 : " + r2.hashCode());
System.out.println("Identity HashCode r1 : " + System.identityHashCode(r1) + " , r2 : " + System.identityHashCode(r2));

HashSet<Robot> rHS = new HashSet<Robot>();

rHS.add(r1);
rHS.add(r2);
rHS.add(r3);

System.out.println("HashSet Size : " + rHS.size());

System.out.println("n==============================n");

HashSet<CustomRobot> cRHS = new HashSet<CustomRobot&gt();

CustomRobot cR1 = new CustomRobot(1999, "R2D2");
CustomRobot cR2 = new CustomRobot(1999, "R2D2");
CustomRobot cR3 = cR1;

System.out.println("cR1 is " + cR1 + " , cR2 is " + cR2 );
System.out.println("cR1 == cR2 result : " + (cR1 == cR2 ? true : false));
System.out.println("cR1 == cR3 result : " + (cR1 == cR3 ? true : false));
System.out.println("Equals between cR1 and cR2 : " + cR1.equals(cR2));
System.out.println("HashCode cR1 : " + cR1.hashCode() + " , cR2 : " + cR2.hashCode());
System.out.println("Identity HashCode cR1 : " + System.identityHashCode(cR1) + " cR2 : " + System.identityHashCode(cR2));

cRHS.add(cR1);
cRHS.add(cR2);
cRHS.add(cR3);

System.out.println("HashSet Size : " + cRHS.size());

}

}

실행한 결과는 다음과 같습니다.

r1 is reference.value.Robot@15db9742 , r2 is reference.value.Robot@6d06d69c
r1 == r2 result : false
r1 == r3 result : true
Equals between r1 and r2 : false
HashCode r1 : 366712642 , r2 : 1829164700
Identity HashCode r1 : 366712642 , r2 : 1829164700
HashSet Size : 2

==============================

cR1 is reference.value.CustomRobot@49b52c2 , cR2 is reference.value.CustomRobot@49b52c2
cR1 == cR2 result : false
cR1 == cR3 result : true
Equals between cR1 and cR2 : true
HashCode cR1 : 77288130 , cR2 : 77288130
Identity HashCode cR1 : 2018699554 cR2 : 1311053135
HashSet Size : 1

두 메소드의 오버라이드를 통해 내용면에서 같은 멤버변수의 값을 갖는 객체라면 물리적으로 동일하지 않더라도 논리적으로는 서로 같은 객체로 인식하는 것을 알 수 있습니다.

HashSet의 경우 중복을 배제하는 컬렉션 클래스이기 때문에 사이즈 또한 이에 기반하여 증가하는 것을 확인 할 수 있으며 HashMap의 경우 이를 이용하여 Key에 일반적으로 사용되는 String이 아닌 별도의 객체를 이용하여 키를 구성하는 것 또한 가능하게 됩니다.

Author Details
Linus Lee aka y1sh former ATiMania.com sysop during 2001-2012. DevSecOps Engineer who likes tech, science, programming and solving problems 🙂
0 0 votes
Article Rating
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments