HashSet - 자바의 hashCode()

2024. 6. 11. 10:18김영한 Java/컬렉션 프레임워크

해시 인덱스를 사용하는 해시 자료 구조는 데이터 추가, 검색, 삭제의 성능이 O(1)로 매우 빠르다. 따라서 많은 곳에서 자주 사용된다. 그런데 앞서 학습한 것 처럼 해시 자료 구조를 사용하려면 정수로 된 숫자 값인 해시 코드가 필요하다. 자바에는 정수 int, Integer 뿐만 아니라 char, String, Double, Boolean등 수많은 다팁이 있다. 뿌난 아니라 개발자가 직접 정의한 Member, User와 같은 사용자 정의 타입도 있다.

이 모든 타입을 해시 자료 구조에 저장하려면 모든 객체가 숫자 해시 코드를 제공할 수 있어야 한다.

 

Object.hashCode()

public class Object{
	public int hashCode();
}
  • 이 메서드를 그대로 사용하기 보다는 보통 재정의 해서 사용한다.
  • 이 메서드의 기본 구현은 객체의 참조값을 기반으로 해시 코드를 생성한다.
  • 쉽게 이야기해서 객체의 인스턴스가 다르면 참조값도 다르다.

 

package collection.set.member;

import java.util.Objects;

public class Member {
    private String id;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    @Override
    public boolean equals(Object object) {
        if (this == object) return true;
        if (object == null || getClass() != object.getClass()) return false;
        Member member = (Member) object;
        return Objects.equals(id, member.id);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id);
    }

    @Override
    public String toString() {
        return "Member{" +
                "id='" + id + '\'' +
                '}';
    }
}
  • IDE가 제공하는 자동 완성 기능을 사용해서 equals(), hashCode()를 생성하자.
  • 여기서는 Member의 id값을 기준으로 equals 비교를 하고, hashCode도 생성한다.
package collection.set;

import collection.set.member.Member;

public class JavaHashCodeMain {
    public static void main(String[] args) {
        //Object의 기본 hashCode는 객체의 참조값을 기반으로 생성
        Object ob1 = new Object();
        Object ob2 = new Object();

        System.out.println("obj1.hashCode" + ob1.hashCode());
        System.out.println("obj2.hashCode" + ob2.hashCode());
        System.out.println(ob1); //16진수로 바꾸어서 출력해줌

        //각 클래스 마다 hashCode를 이미 오버라이딩 해두었다.
        Integer i = 10;
        String strA= "A";
        String strAB = "AB";

        System.out.println("10.hashCode() = " + i.hashCode());
        System.out.println("'A'.hashCode() = " +strA.hashCode());
        System.out.println("'AB'.hashCode() = " +strAB.hashCode());

        //hashCode는 마이너스 값이 들어올 수 있다.
        System.out.println("-1.hashCode = "+ Integer.valueOf(-1).hashCode()); //-1

        //둘은 같을까?,인스턴스는 다르지만, equals는 같다
        Member member1 = new Member();
        Member member2 = new Member();

        //equals, hashCode를 오버라이딩 하지 않은 경우와, 한 경우를 비교
        System.out.println("(member1 == member2) = " + (member1==member2));
        System.out.println("(member1 equals member2) = " + (member1.equals(member2)));
        System.out.println("member1.hashCode() = " + member1.hashCode());
        System.out.println("member2.hashCode() = " + member2.hashCode());
    }


}

 

실행 결과

obj1.hashCode189568618
obj2.hashCode664223387
java.lang.Object@b4c966a
10.hashCode() = 10
'A'.hashCode() = 65
'AB'.hashCode() = 2081
-1.hashCode = -1
(member1 == member2) = false
(member1 equals member2) = true
member1.hashCode() = 31
member2.hashCode() = 31

 

Object의 해시 코드 비교

  • Object가 기본으로 제공하는 hashCode()는 객체의 참조값을 해시 코드로 사용한다. 따라서 각각의 인스턴스 마다 서로 다른 값을 반환한다.
  • 그 결과 ob1, ob2는 서로 다른 해시 코드를 반환한다.

Java의 기본 클래스의 해시코드

  • Integer, String 같은 자바의 기본 클래스들은 대부분 내부 값을 기반으로 해시 코드를 구할 수 있도록 hashCode() 메서드를 재정의해 두었다
  • 따라서 데이터의 값이 같으면 같은 해시코드를 반환한다.
  • 해시 코드의 경우 정수를 반환하기 때문에 마이너스 값이 나올 수 있다.

 

동일성과 동등성 복습

Object는 동등성 비교를 위한 equals()메서드를 제공한다.

자바는 두 객체가 같다는 표현을 2가지로 분리해서 사용한다.

  • 동일성(Identity) : ==연산자를 사용해서 두 객체의 참조가 동일한 객체를 가리키고 있는지 확인
  • 동등성(Equality) : equals()메서드를 사용하여 두 객체가 논리적으로 동등한지 확인

쉽게 이야기해서 동일성(Identity)은 물리적으로 같은 메모리에 있는 객체인지 참조값을 확인하는 것이고, 동등성은 논리적으로 같은지 확인하는 것이다.

동일성은 자바 머신 기준이고 메모리의 참조가 기준으로 물리적이다. 동등성은 보통 사람이 생각하는 논리적인 것에 기준을 맞춘다. 따라서 논리적이다.

 

동등성은 예를 들면 같은 회원 번호를 가진 회원 객체가 2개 있다고 가정해보자.

User a = new User("id-100")
User b = new User("id-100")

이런 경우 물리적으로 다른 메모리에 있는 객체이지만, 논리적으로 같다고 표현할 수 있다.

따라서 동일성은 다르지만, 동등성은 같다.

 

문자의 경우도 마찬가지이다.

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

이 경우 물리적으로는 각각의 "hello"문자열이 다른 메모리에 존재할 수 있지만, 논리적으로 같은 "hello"라는 문자열이다.

 

직접 구현하는 해시 코드

  • Member는 hashCode()를 재정의했다.
  • hashCode() 를 재정의 할 때 Object.hash()에 해시 코드로 사용할 값을 지정해주면 쉽게 해시 코드를 생성할 수 있다.
  • hashCode()를 재정의 하지 않으면 Object가 기본으로 제공하는 hashCode()를 사용하게 된다. 이것은 객체의 참조값을 기반으로 해시 코드를 제공한다. 따라서 회원의 id가 같아도 인스턴스가 다르면 다른 해시 코드를 반환하게 된다.
  • hashCOde()를 id를 기반으로 재정의한 덕분에 인스턴스가 달라도 id값이 같으면 같은 해시 코드를 반환한다.
  • 따라서 인스턴스가 다른 member1, member2 둘다 같은 해시 코드를 반환하는 것을 확인할 수 있다.

정리

자바가 기본으로 제공하는 클래스 대부분은 hashCode()를 재정의해두었다.

객체를 직접 만들어야 하는 경우에 hashCode()를 재정의하면 된다.

hashCode()만 재정의하면 필요한 모든 종류의 객체를 해시 자료 구조에 보관할 수 있다.

정리하면 해시 자료 구조에 데이터를 저장하는 경우 hashCode()를 구현해야 한다.

 

이제 hashCode()를 사용해서 모든 데이터 타입을 저장할 수 있는 MyHashSetV2를 만들어보자.