Java equals,hashcode

Terror·2024년 9월 17일
0

Equlas()

  • 어떤 " 두 참조 변수의 값이 같은지,다른지 동등 여부"를 확인하는 것이 equals() 메서드 이다
  • 대표적으로 String을 예시로 들 수 있다.
    public static void String_비교() {
        String s1 = new String("Hello");
        String s2 = new String("Hello");

        System.out.println(s1 == s2); // 주소 비교 false
        System.out.println(s1.equals(s2)); // 값 비교 true
    }
    
    public static void main(String[] args) {
        String_비교();
    }    

  • 그렇다면 문자열이 아닌 클래스 자료형의 객체 데이터일 경우 equals() 메서드는 어떻게 다뤄질까?
  • " 비교할 대상이 객체라면 객체의 주소를 이용" 하여 비교한다
  • 즉 객체 자체를 비교할때는 ==,equals() 나 둘이 동일하다고 생각하면 된다
package equlas_hashcode;

public class Person {
    private PersonId id;

    public Person(PersonId id) {
        this.id = id;
    }

    public static class PersonId {
        private long value;

        public PersonId(long value) {
            this.value = value;
        }
    }
}

    public static void 객체_비교() {
        Person.PersonId p1 = new Person.PersonId(1L);
        Person.PersonId p2 = new Person.PersonId(1L);
        Person person1 = new Person(p1);
        Person person2 = new Person(p2);

        System.out.println(person1 == person2);
        System.out.println(person1.equals(person2));
    }
    
    public static void main(String[] args) {
        객체_비교();
    }    

  • 둘다 객체의 주소를 비교하기 때문에, false가 나오는 모습이다

Equals() 메서드 오버라이딩

  • 잘 생각해보자
  • 위의 person1,person2 각자 다른 힙메모리 영역에 저장되고있으니 서로의 객체 주소값을 비교하였을때 false가 나오는것은 당연하다
  • 하지만 이는 컴퓨터적인 관점에서 바라보았을 때다
  • 우리의 관점으로보면 둘다 Person 객체이고, 둘다 동일한 식별자값인 id 1을 가지고있지않은가 ?
  • 이를 위해서 있는것이, equals() 메서드의 오버라이딩이다
  • 그렇다면 이제 서로의 id의 값이 일치할때 동등하다는 사실을 알리게 오버라이딩 하여보자
package equlas_hashcode;

import java.util.Objects;

public class Person {
    private PersonId id;

    public Person(PersonId id) {
        this.id = id;
    }

    public static class PersonId {
        private long value;

        public PersonId(long value) {
            this.value = value;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof PersonId personId)) return false;
            return value == personId.value;
        }
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person person)) return false;
        return Objects.equals(this.id, person.id);
    }
}
  • 맨처음 Person 객체에있는 eqauls가 호출되고, PersonId에 있는 eqauls가 호출되는 방식이다

    Objects class

Hashcode

  • hashcode 메서드는 "객체 주소 값"을 이용해서 해싱(hashing) 기법을 통해 "해시코드"를 만든후 반환한다
  • 따라서 " 서로 다른 두 객체 같은 해시코드를 가질 수 없게된다
  • 그래서 해시코드는 객체의 지문 이라고도 한다
  • 즉 객체의 주소값으로 만든 고유번호 정도로 생각하면된다
    public static void 객체_해쉬코드_찍어보기() {
        Person.PersonId p1 = new Person.PersonId(1L);
        Person.PersonId p2 = new Person.PersonId(1L);
        Person person1 = new Person(p1);
        Person person2 = new Person(p2);

        System.out.println(person1.hashCode());
        System.out.println(person2.hashCode());
    }
    public static void main(String[] args) {
        객체_해쉬코드_찍어보기();
    }    

  • hashcode를 까고들어가보면 native라는 이상한 모양을 하고있다
  • native 키워드가 들어간 메서드는 OS가 가지고있는 메서드이다
  • JVM에 공부해보았다면, JNI에 대해 들어본적이 있을 것이다
  • JNI는 C나 저수준의 언어로 작성된 native 코드를 JVM에 적재시키고 실행해주는 머신인데, 이 natvie코드중 하나가 hashCode() 이다
  • 메서드안의 내용은 OS에 C언어로 작성되어 있어, 내용은 볼 수 없고 사용만 가능하다

HashCode 오버라이딩

  • HashCode를 오버라이딩 할때는 단독으로 오버라이딩 하는것이아닌, equlas() 메서드도 같이 재정의 해주어야한다는데
  • 결론부터 말하면 hash 값을 사용하는 Collection Framework(HashSet, HashMap, HashTable)을 사용할때 문제가 발생하기 때문이다
  • 먼저 equlas만 오버라이딩하였을떄 List,Set를 활용해서 값을 추가했을때를 확인해보자

List

    public static void 해쉬코드_오버라이딩안하고_리스트_사이즈_재보기() {
        List<Person> personList = new ArrayList<>();
        Person.PersonId p1 = new Person.PersonId(1L);
        Person.PersonId p2 = new Person.PersonId(1L);
        Person person1 = new Person(p1);
        Person person2 = new Person(p2);
        personList.add(person1);
        personList.add(person2);
        System.out.println(personList.size());
    }
    public static void main(String[] args) {
        해쉬코드_오버라이딩안하고_리스트_사이즈_재보기();
    }    

  • 당연히 2가 나오는 모습이다 그렇다면 이번엔 Set에 추가해보겠다

Set

    public static void 해쉬코드_오버라이딩안하고_세트_사이즈_재보기() {
        Set<Person> personList = new HashSet<>();
        Person.PersonId p1 = new Person.PersonId(1L);
        Person.PersonId p2 = new Person.PersonId(1L);
        Person person1 = new Person(p1);
        Person person2 = new Person(p2);
        personList.add(person1);
        personList.add(person2);
        System.out.println(personList.size());
    }    
    public static void main(String[] args) {
        해쉬코드_오버라이딩안하고_세트_사이즈_재보기();
    }    

  • 분명 동일한 식별자값을 주고, 우리는 서로의 식별자값이 동일하다면 같은 객체로 취급하기로 하였기때문에 Set에 1이 나올것 같았지만, 2가 덩그라니 찍히는 모습이다
  • person1,person2는 "논리적" 으로는 같을지라도, 서로의 해쉬코드가 다르기 때문에 Set에 들어가지는 모습이다

HashCode와 equals의 동작 순서

  • 위처럼 동작되는 이유는 hash 값을 사용하는 Collection(HashMap,HashSet,HashTable)은 객체가 "논리적"으로 같은지 비교할 때 아래와 같은 과정을 거치기 때문이다
  • 가장 먼저 Hashcode에 대해 비교하기 때문에, 다른객체로 판단되어서 바로 추가되는 모습이다

Equals,Hashcode 동시 재정의

package equlas_hashcode;

import java.util.Objects;

public class Person {
    private PersonId id;

    public Person(PersonId id) {
        this.id = id;
    }

    public static class PersonId {
        private long value;

        public PersonId(long value) {
            this.value = value;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) return true;
            if (!(o instanceof PersonId personId)) return false;
            return value == personId.value;
        }

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

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (!(o instanceof Person person)) return false;
        return Objects.equals(this.id, person.id);
    }

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

  • 오버라이딩후 결과는 잘 나오는것을 확인 할 수 있다

    여기서 숙지해야할것은, 우리가 만든 커스텀한 객체의값을 비교할떄, Set에 추가해야할때 eqauls,hashcode를 오버라이딩하였음을 인지 하여야한다.
    자바에서 만들어놓은 객체 데이터를 적재할때는 문제가 되지 않는다

identityHashCode

  • hashCode() 메서드의 본래 목적은 객체의 주소값을 기반으로 해싱해서 숫자값을 반환하는 것이다, 이를 통해 객체가 같은지 같지 않은지 판별 할 수 있다
  • 그런데 우리는 equlas(),hashcode()를 오버라이딩 함으로써, 객체의 필드값으로 해싱하게 되었다
  • 만약 객체의 주소값을 해시코드로 얻어야하는 상황을 대비해 만들어진것이다
    public static void 해쉬코드_오버라이딩_했을때와_독립적인_해쉬코드_얻기() {
        Set<Person> personList = new HashSet<>();
        Person.PersonId p1 = new Person.PersonId(1L);
        Person.PersonId p2 = new Person.PersonId(1L);
        Person person1 = new Person(p1);
        Person person2 = new Person(p2);
        System.out.println("person1.hashCode() = " + person1.hashCode());
        System.out.println("person2.hashCode() = " + person2.hashCode());
        System.out.println("System.identityHashCode(person1) = " + System.identityHashCode(person1));
        System.out.println("System.identityHashCode(person2) = " + System.identityHashCode(person2));
    }
    public static void main(String[] args) {
        해쉬코드_오버라이딩_했을때와_독립적인_해쉬코드_얻기();
    }    

한줄 요약

  • hashCode()는 객체의 주소값을 숫자로 변환시킨 모양이다
  • hashCode(),equlas()를 동시에 재정의 하라는것은, 개발자가 커스텀한 클래스를 만들고 그 클래스에서 중복되지않는 객체들을 Set를 통해서 넣으려고할때 hashCode()가 먼저비교되고, equlas()가 실행되어 동등성을 판단하기 떄문에 반드시 둘다 재정의 해줘야한다 (자바에서 미리만들어둔 데이터들에 대해서는 무관하다)
  • identityHashCode는 오버라이딩 하여 변형된 해쉬코드값이아닌, 실제 객체의 메모리 주소값을 해쉬코드로 바꾼 순수한 형태를 반환시켜준다

끝!

참조 블로그

https://inpa.tistory.com/entry/JAVA-%E2%98%95-equals-hashCode-%EB%A9%94%EC%84%9C%EB%93%9C-%EA%B0%9C%EB%85%90-%ED%99%9C%EC%9A%A9-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0

profile
테러대응전문가

0개의 댓글