java.lang.Object의 메소드인 hashCode()와 equals()는 보편적으로 사용되는 메소드는 아니지만 상황에 따라 오버라이드 되어 사용되기도 합니다. 오버라이드시 어떤 기능을 할 수 있는지 한번 알아보도록 하겠습니다.
hashCode()
Object에 포함된 기본 상태의 hashCode() 메소드는 객체에 대한 메모리 주소 매핑 정보를 기반으로 정수 값을 반환 합니다. Object 클래스의 소스를 보면 알 수 있겠지만 hashCode() 메소드는 다음과 같이 작성 되어 있습니다.
1 | public native int hashCode(); |
hashCode() 메소드가 JVM상에 네이티브 코드로 직접 구현되어 있다는 것을 의미하며 메모리 주소를 특정한 지역 정보 형태로 제공한다는 것을 알 수 있습니다. 이런 특징이 있음에도 불구하고 이 메소드는 구현체에서 오버라이드 될 수 있습니다.
equals()
equals() 메소드는 두개의 객체가 동일한지 비교할 수 있는 특별한 메소드 입니다. 자바에는 동일함을 비교하는 방식이 두 종류가 있는데 하나는 "==" 연산자를 사용하는 것이며 다른 하나는 "equals()" 메소드 입니다. equals() 메소드의 경우 다음과 같이 작성 되어 있습니다.
1 2 3 | public boolean equals(Object obj) { return ( this == obj); } |
오버라이드가 가능하다는 측면에서 둘의 차이를 좀더 생각해 보자면 객체의 비교에 있어 ".equals()"는 서로 같은 값을 갖는 관계를 이야기 하며 "==" 연산자의 경우 물리적으로 완전히 동일함을 이야기 한다고 볼 수 있을 것 입니다.
규약
hashCode()와 equals()는 결국 상호 연관관계에 있는 메소드로 둘은 동시에 오버라이드 되어야 하며 그렇지 않으면 API문서에 나와있는 메소드 규약을 위반하게 됩니다.
테스트
테스트를 위해 hashCode()와 equals()모두를 오버라이드한 클래스와 둘다 변경하지 않은 클래스를 작성 하였습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | 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; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 | 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 ; } } |
이 둘의 차이를 알아보기 위해 다음과 같은 테스트 클래스를 작성하였습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | 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>(); 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()); } } |
실행한 결과는 다음과 같습니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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이 아닌 별도의 객체를 이용하여 키를 구성하는 것 또한 가능하게 됩니다.