[F-lab 모각코 챌린지 10일차] TIL

JeongheeKim·2023년 6월 10일

TIL

목록 보기
10/66
post-thumbnail

hashCode가 메모리 주소를 나타낸다고 오해했었는데,아래 코드를 통해 재밌는 사실을 알 수 있었다.

  • hashCode는 객체의 메모리 주소가 아니다.

    As much as is reasonably practical, the hashCode method defined by class Object does return distinct integers for distinct objects. (This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the Java™ programming language.)

    hashCode ≠ 메모리 주소 이지만 오라클 공식 문서를 보면 어떠한 관계가 있음을 뜻하는거같다.
  • 16진수인 hashCode를 10진수 변환하면, toString()시 @뒤에 리턴되는 값이 나온다.
    • toString메서드를 확인 해 보니 hashCode를 10진수로 변경한 값을 리턴하고 있었다.🤪
import org.openjdk.jol.vm.VM;

/**
 * @author jhkim
 * @date 2023-06-10
 */
public class HashCodeTest {
    public static void main(String[] args) {
        Person person1 = new Person(1,"ken");

        System.out.println("person1 Memory Address = " + VM.current().addressOf(person1));
        System.out.println("person1 hashCode = " + person1.hashCode());
        System.out.println("person1.toString() = " + person1);
        System.out.println("person1 Integer.toHexString = " + Integer.toHexString(person1.hashCode()));
    }
}
person1 Memory Address = 31878267520
person1 hashCode = 485815673
person1.toString() = Person@1cf4f579
person1 Integer.toHexString = 1cf4f579

Q1. hashcode는 정수값이다. 이 정수값이 주소값을 가리킬 수 있을까?

해시코드란 객체를 식별하는 정수를 뜻한다.

Object클래스의 hashCode()는 객체의 메모리 번지를 이용해서 해시코드를 생성하여 객체마다 다른 정수값을 리턴한다. 객체의 주소값을 해싱 알고리즘을 통해 16진수로 표현된 integer값으로 리턴한다.

This is typically implemented by converting the internal address of the object into an integer, but this implementation technique is not required by the Java™ programming language.

이는 오라클 공식문서 hashCode()에 대해 보면 주소값을 16진수 integer값으로 변환된것임을 확인 할 수 있다.

Q2. hashcode는 equals나 동등연산시 사용될까?

java에서는 hashCode를 equals()연산 시 사용한다.

  • equals() 메서드 만으로 비교가 가능하지만, 정확한 비교를 위해 hashCode를 같이 사용한다.
  • hashCode() java doc
    /**
         * Returns a hash code value for the object. This method is
         * supported for the benefit of hash tables such as those provided by
         * {@link java.util.HashMap}.
         * <p>
         * The general contract of {@code hashCode} is:
         * <ul>
         * <li>**Whenever it is invoked on the same object more than once during
         *     an execution of a Java application, the {@code hashCode} method
         *     must consistently return the same integer, provided no information
         *     used in {@code equals} comparisons on the object is modified.
         *     This integer need not remain consistent from one execution of an
         *     application to another execution of the same application.**
         * <li>**If two objects are equal according to the {@link
         *     equals(Object) equals} method, then calling the {@code
         *     hashCode} method on each of the two objects must produce the
         *     same integer result.**
         * <li>**It is <em>not</em> required that if two objects are unequal
         *     according to the {@link equals(Object) equals} method, then
         *     calling the {@code hashCode} method on each of the two objects
         *     must produce distinct integer results.  However, the programmer
         *     should be aware that producing distinct integer results for
         *     unequal objects may improve the performance of hash tables.**
         * </ul>
         *
         * @implSpec
         * As far as is reasonably practical, the {@code hashCode} method defined
         * by class {@code Object} returns distinct integers for distinct objects.
         *
         * @return  a hash code value for this object.
         * @see     java.lang.Object#equals(java.lang.Object)
         * @see     java.lang.System#identityHashCode
         */
        @IntrinsicCandidate
        public native int hashCode();

hashcode의 javadoc을 보면 세가지 특징이 있다. 이 특징들로 하여금 equals()와 함께 동등 연산이 가능하다.

  1. 동일한 java application에 대해 실행 되어도 equals의 비교 항목들이 변경되지 않는 이상 동일한 integer값을 리턴해야한다.
  2. 두 객체가 equals 결과가 true라면, 두 객체는 동일한 hashCode를 가진다.
  3. 두 객체가 equals 결과가 false라고해서, hashCode가 무조건 다른것은 아니다. 하지만, 이 경우 서로 다른 int값을 제공하는 경우 hashtable의 성능을 향상시키는데 도움이된다. (hashTable구조에 값이 저장될때 키를 알고 데이터를 찾기때문에 hashCode가 다를 경우 속성능 향상에 도움이 될 수 있다.)

Q3. hashcode를 어디서 쓰길래 오버라이딩할때 유의하라는걸까?

hashCode는 객체의 필드값을 조합하여 객체가 가진 고유한 정수값을 리턴한다.

그러므로 동등 비교 시 동일한 객체임을 보장하기 위해서는 동일한 hashCode()를 리턴하도록 재정의 해야한다.

두 객체가 논리적으로 같다면(equals() = true) hashCode도 동일해야한다. 만약 hashCode를 오버라이딩 하지 않으면 부모클래스인 Object클래스의 hashCode를 리턴하므로 논리적으로 같은 객체에서 사용하는 필드값으로 재정의 된 hashCode를 재정의 해야한다.

아래 질문에 대해서도 한번 찾아보세요

Q4. hashcode는 어떻게 생성될까?

String 클래스에 overring된 hashcode 생성 방식은 아래와 같다.

jdk 11의 String hashCode()

Object 클래스의 hashCode()를 오버라이딩 한 String 클래스의 hashCode 코드를 살펴보았다. jdk8, jdk 11이 차이가 나는데 직관적인 jdk8의 코드를 확인해보려 한다.

public static void main(String[] args) {
        String myStr = "hello";
        System.out.println(myStr.hashCode());
}
/** Cache the hash code for the string */
private int hash; // Default to 0

/** The value is used for character storage. */
private **final** char value[];//String값이 배열에 들어간다. final이므로 immutable하다.

public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }
  • 변환된 hash코드는 hash라는 멤버변수에 저장되고, 아무것도 저장되어있지 않을 경우 기본값 0이 저장되어있다.
  • 문자열을 for문으로 한자리씩 읽으면서 ascii code로 변환한 값과 h에 31을 곱한값을 더한다.
  • 31을 곱하는 이유
    • 정확히 이해되지않지만 31이 소수이면서 홀수여서 선택되었다고 한다.

일반 객체에서 equals, hashCode를 재정의 했을 경우를 살펴보자.

public class Person {
    private int seq;
    private String name;

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

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

public static int hash(Object... values) {
        return Arrays.hashCode(values);
}

public static int hashCode(Object a[]) {
        if (a == null)
            return 0;

        int result = 1;

        for (Object element : a)
            result = 31 * result + (element == null ? 0 : element.hashCode());

        return result;
    }

String클래스에서 hashCode를 정의하는 부분과 동일하게 필드를 구성하는 구성 요소들을 통해 hashCode를 생성하고 있다.

Q5. hashcode는 중복이 일어나지 않을까?

hashCode는 Integer타입이므로 int값의 범위 제한이 있다.(2147483648 to 2147483647) 그러므로 hashCode 중복이 발생한다.

hashCode중복때문에 hash함수를 이용하는 associative array구현체는 메모리를 절약하기 위해 정수범위 보다 작은 원소를 담을 수 있는 배열을 사용한다.

해시 충돌을 피할 수 있도록 사용하는 방식에는 두가지가 있다.

  1. Open Addressing

    https://en.wikipedia.org/wiki/Hash_table

    1. 데이터를 삽입하려는 해시 버킷이 이미 사용중인 경우 다른 해시 버킷에 해당 데이터를 삽입하는 방식
  2. Separate Chaning

    https://en.wikipedia.org/wiki/Hash_table

    1. 충돌 발생 시 추가적인 메모리를 사용해(linkedList를 통해) value 뒤에 값을 저장하는 방식

[참고][Object (Java Platform SE 8 )](https://docs.oracle.com/javase/8/docs/api/java/lang/Object.html#hashCode--)

What is hashing and how does it work?

https://www.youtube.com/watch?v=GK-oE509wKA&ab_channel=한빛미디어

0개의 댓글