본문 바로가기

JAVA

Equals 와 HashCode

1. Object 클래스의 Equals 와 Hashcode 메소드


자바의 모든 클래스는 Object를 상속받고 있고, Object 클래스에는 equals 와 hashcode 메소드가 존재한다.

 

즉, 우리가 클래스를 만들게 되면 Object 클래스의 메소드를 Overriding 이 가능하다는 것이다.

 

1. equals() 란

 

equals 메소드는 2개의 객체가 동일한지 검사하기위해 사용된다. 

 

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

 

메소드를 직접 살펴보면 두객체의 참조하는곳이 같은지를 리턴한다.

 

즉, 2개의 객체가 가리키는곳이 동일하면 true 아니면 false 를 리턴하는 것이다.

 

다음의 코드를 살펴보자

 

public class Main {
    public static void main(String[] args) {
        String str1 = new String("a");
        String str2 = new String("a");

        System.out.println("str1 == str2 : "+ (str1==str2)); // false
        System.out.println("str1 equals str2 : "+ str1.equals(str2)); //true
    }
}

 

String 형 데이터 str1과 str2를 String Constant Pool에 생성하지 않고 new 예약어를 사용하여 Heap 메모리에 저장했다.

 

== 비교 연산자는 기본 자료형 변수를 비교했을때는 값이 같은지를 판단하지만,

참조 자료형에 사용할시엔 값이 같아도 객체의 주소값은 다르기 때문에 false 가 출력되는것을 확인할 수 있다.

 

하지만, str1과 str2 를 리터럴("")로 선언했을 시에는 String Constant Pool에 생성하여 값이 같다면 동일한 객체의 주소를 가리키기 때문에 true가 출력될 것이다.

 

이번엔 equals 연산자의 결과를 보면 true로 출력되는것을 확인할 수 있다,

 

분명 다른 주소를 가지고있는 str1, str2 인데 어째서 true로 출력되는것인가?

 

그답은 String 클래스에는 equals 메소드가 Overriding 해서 문자를 비교하는 코드를 추가했기 때문이다.

 

public boolean equals(Object anObject) {
    if (this == anObject) {
        return true;
    }
    if (anObject instanceof String) {
        String anotherString = (String)anObject;
        int n = value.length;
        if (n == anotherString.value.length) {
            char v1[] = value;
            char v2[] = anotherString.value;
            int i = 0;
            while (n-- != 0) {
                if (v1[i] != v2[i])
                    return false;
                i++;
            }
            return true;
        }
    }
    return false;
}

 

1. hashCode() 란

 

hashCode() 는 런타임시 객체의 유일한 Integer 값을 반환한다. 

 

public native int hashCode();

 

여기서 native 예약어는 메소드가 JNI(Java Native Interface)라는 native code를 이용해 구현되었음을 의미한다. 이는 메소드에만 적용가능한 예약어이며 java 가 아닌 C/C++ 언어로 구현된 부분을 JNI 를 통해서 java에서 이용하고자 할대 사용한다.

 

또한 hashCode()는 HashTable 같은 자료구조를 사용할 때 데이터가 저장되는 위치를 결정하기 위해 사용한다.

 

컬렉션 프레임워크(Collection Framework) (4) - 해시테이블(Hashtable), 해시맵(HashMap)

 

컬렉션 프레임워크(Collection Framework) (4) - 해시테이블(Hashtable), 해시맵(HashMap)

1. Hashtable vs HashMap 앞서 HashMap 클래스에 있는 메소드의 사용법에 대해 알아보았다. 2021.12.28 - [자바] - 컬렉션 프레임워크(Collection Framework) (3) - Map 컬렉션 프레임워크(Collection Framework)..

dev-cool.tistory.com

 

2. equals() 와 hashCode() 의 Overrride


그렇다면 equals() 와 hashCode()를 직접 Overriding해서 사용해보도록 하자

 

간단한 Dto 클래스를 만들어보면

 

public class MemberDto {
    private String name;
    private String phone;

    public String getName() {
        return name;
    }

    public String getPhone() {
        return phone;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

 

public class EqualTest {
    public static void main(String[] args) {
        MemberDto memberDto1 = new MemberDto();
        MemberDto memberDto2 = new MemberDto();

        memberDto1.setName("cool");
        memberDto2.setName("cool");

        System.out.println(memberDto1.equals(memberDto2)); //false
    }
}

 

MemberDto 클래스에는 멤버 변수로 name 과 phone이 있다.

 

memberDto1, memberDto2 객체를 생성하고, name 변수에 같은 값을 입력한 후 equals 메소드를 사용해서 비교해보면 false가 출력되는 것을 확인할 수 있다.

 

이는 MemberDto 클래스에 equals 메소드가 Override 되지 않았으므로 Object 의 equals가 호출되어

다른 주소를 가지고 있는 객체 2개를 비교해서 false를 출력하는 것이다. equals 메소드를 Override 해보자

 

public class MemberDto {
    private String name;
    private String phone;

    public String getName() {
        return name;
    }

    public String getPhone() {
        return phone;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
    
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MemberDto memberDto = (MemberDto) o;
        return Objects.equals(name, memberDto.name) &&
                Objects.equals(phone, memberDto.phone);
    }
}

 

여기서 작성한 equals 메소드는 인텔리제이의 기능으로 완성한 것이며 다시한번 main 을 실행하면 true를 반환하는 것을 볼 수 있다.

 

그렇다면 equals 메소드만 Override 하면 두 객체는 같다고 볼 수 있는가?

 

답을 알기 위해서 Java의 자료구조중 하나인 HashSet에 MemberDto 객체를 생성해서 확인해보자.

 

public class HashcodeTest {
    public static void main(String[] args) {
        Set<MemberDto> memberDtoSet = new HashSet<>();
        MemberDto memberDto1 = new MemberDto();
        MemberDto memberDto2 = new MemberDto();

        memberDto1.setName("cool");
        memberDto2.setName("cool");

        memberDtoSet.add(memberDto1);
        memberDtoSet.add(memberDto2);

	System.out.println(memberDto1.equals(memberDto2)); //true
        System.out.println(memberDtoSet.size());
    }
}

 

HashSet 의 요소들은 중복이 불가능한것을 알고있다면, equals() 를 Override 했기때문에 true가 나왔으니 당연히 HashSet 의 요소들의 갯수는 1개가 될거라고 예상할 수 있다.

 

하지만 결과는 2로 출력된다.

 

이전의 포스팅에도 올려놨지만 Hash 관련 클래스들은 데이터를 저장할때 hashcode() 값으로 인덱싱하여 자료를 저장하게 된다.

 

컬렉션 프레임워크(Collection Framework) (4) - 해시테이블(Hashtable), 해시맵(HashMap)

 

컬렉션 프레임워크(Collection Framework) (4) - 해시테이블(Hashtable), 해시맵(HashMap)

1. Hashtable vs HashMap 앞서 HashMap 클래스에 있는 메소드의 사용법에 대해 알아보았다. 2021.12.28 - [자바] - 컬렉션 프레임워크(Collection Framework) (3) - Map 컬렉션 프레임워크(Collection Framework)..

dev-cool.tistory.com

 

따라서 우리는 아직 hashCode() 를 Override 하지 않았기 때문에 memberDto1 과 memberDto2 는 다른 Hash값을 가지게 되서 다른 객체라고 판단하고 따로 저장하게 되는것이다.

 

그렇다면 MemberDto 클래스에 hashCode()를 Override 해보자

 

public class MemberDto {
    private String name;
    private String phone;

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        MemberDto memberDto = (MemberDto) o;
        return Objects.equals(name, memberDto.name) &&
                Objects.equals(phone, memberDto.phone);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, phone);
    }

    public String getName() {
        return name;
    }

    public String getPhone() {
        return phone;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }
}

 

equals() 와 마찬가지로 인텔리제이에서 제공하는 기능을 이용하여 hashCode()를 Override 하였다.

 

이제 다시 출력을해보면 HashSet의 크기는 1로 출력되는 것을 알 수 있다.

 

3. equals()와 hashCode()를 사용하는 전략


이제 우리는 equals()와 hashCode()를 Override 해서 두 객체가 같은지 비교하는 기능을 수행하는것을 알게되었다.

 

이제 equals()와 hashCode()를 Override 하는 전략을 알아보자

 

1. hashCode() 와 equals()를 생성하기 위해서 같은 필드를 이용하여야 한다 ( 예시 : memberDto의 name )

2. equals()는 반드시 일관되어야한다 ( 객체가 수정된것이 아니라면 값이 항상 동일해야한다. )

3. 만약 a.equals(b) 가 true라면 a.hashCode()==b.hashCode() 역시 true여야 한다.

4. equals를 Override 했다면 hashCode()도 Override 해야한다.

 

참고 : https://howtodoinjava.com/java/basics/java-hashcode-equals-methods/