Lombok @Data의 @EqualsAndHashCode이 뭐하는 애일까?

신창호·2022년 7월 29일
0

Lombok라이브러리

목록 보기
2/2
post-thumbnail

lombok 라이브러리를 애용하다보면, 자주 사용하게 되는 애노테이션들이 있다. (너무 편리하게 애용하고 있다)

하지만, 그마저도 자주쓰는 애노테이션끼리 묶어 @Data라는 애노테이션이 있는데,

@Data

  • @Getter , @Setter , @ToString, @EqualsAndHashCode , @RequiredArgsConstructor를 자동으로 적용해준다
  • @Getter@Setter 그리고 @RequiredArgsConstructor의 경우에는 별도로도 자주 사용하기에, 각 애노테이션의 역할을 잘 알고 있다.
  • @ToString도 직관적으로 해당 클래스를 toString으로 반환할 수 있게 해주는 역할이다.
  • @EqualsAndHashCode은 뭔가 익숙하지 않은 애노테이션인데, 왜 굳이 @Data안에 있을까 궁금해져서 파헤치기 시작해봤다.






@EqualsAndHashCode

@EqualsAndHashCode애노테이션의 오해

  • 이름에서도 알 수 있듯이 equals, hashCode 자동 생성해주는 애노테이션이다.
  • 하지만, 이 둘에 대해 찾아보면, 대부분 동일성과 동등성을 가지고 표현한다.
    • 인터넷에서 equals, hashCode의 대해 찾아보면
      • equals는 두 객체의 내용이 같은지, 동등성(equality) 를 비교하는 연산자
      • hashCode는 두 객체가 같은 객체인지, 동일성(identity) 를 비교하는 연산자
  • equals는 String비교연산자로 유명하고 Hashcode는 객체 주소값을 변환한 값이라고 다들 알고 있어서 언뜻보면 맞는말 같고 그렇게 보인다.

하지만, 아래 한번 예시를 보자



예시

hashCode()

  • hashCode()로 반환되는 값 확인해보기
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(@ModelAttribute HelloData helloData){
        HelloData helloData2 = new HelloData();
        helloData2.setUsername("hello");
        helloData2.setAge(20);
        int code1 = helloData.hashCode();  //99166983
        int code2 = helloData2.hashCode(); //99166983
        System.out.println(helloData.equals(helloData2)); // true
    return "ok";
}
  • hashCode()의 메소드를 보면 반환값이 integer값이다.
  • helloData인스턴스와 helloData2인스턴스는 다른 객체이지만, 초기화값은 똑같이 해준것이다. 아래사진의 디버그 값을 보면 알 수 있다.
  • 하지만, 이 둘의 인스턴스주소값은 다르지만, hashcode값은 똑같이 나온다는 것을 알 수 있다.

분명 다른 주소값을 갖는 객체들인데, 왜 Hashcode값은 동일할까?
hashcode()메소드 내부값을 들여다보았따.



hashcode() 메소드 내부

   public int hashCode() {
           int PRIME = true;
           int result = 1;
           result = result * 59 + this.getAge();
           Object $username = this.getUsername();
           result = result * 59 + ($username == null ? 43 : $username.hashCode());
           return result;
           }
   }
  • @Data어노테이션의 hashcode()를 확인하기위해, Build를 돌려, 생성된 class파일을 찾아보았다.

  • 여기서 알게된 놀라운 사실은 hashcode는 객체 주소값이 아닌객체 내부의 있는 값만 다룬다는 점이다.

  • 그래서 한가지 실험을 해보았다.






만약, 동일한 필드값(내부의 값)을 가지는 객체 2개를 만들고
각각, 인스턴스를 만드는데, 내부의 값이 같다면? 어떻게 될까?

  • HelloData.java
@Data
public class HelloData {
    private String username;
    private int age;
}
  • HelloData2.java
@Data
public class HelloData2 {
    private String username;
    private int age;
}
  • 테스트
@ResponseBody
@RequestMapping("/model-attribute-v1")
public String modelAttributeV1(HelloData helloData){
    HelloData2 helloData2 = new HelloData2();
    helloData2.setUsername("hello");
    helloData2.setAge(20);
    int code1 = helloData.hashCode();
    int code2 = helloData2.hashCode();
    System.out.println(code2 == code1); // true
    System.out.println(helloData2.equals(helloData)); // false
    return "ok";
}
  • 원하는대로, hashcode의 값은 서로 일치하였으나, 동일한 객체임을 확인하는 equals()에서는 false가 나온다는 것을 알 수 있었다.

    • 결국 객체 내부의 값이 같은지 확인하는 값이 hashcode()이며,
    • 같은 객체인지 확인하는 값이 equals()라는 것을 깨닫는 시간이었다.
  • 내부 필드값이 동일하다면, hashcode값도 계속해서 동일함을 알 수 있었다.

  • hashMap<Integer, String>과 같은 hashMap의 key값을 구분할 때,

    • 같은 Integer타입임에도 내부 값이 다름으로, hashcode를 기반으로 구분하여, 좋은 성능을 낼 수 있다고 한다.






정리

  • lombok @EqualsAndHashCode애노테이션에서 생성하는 hashcode() equals()은 아래와 같다.
    • 두 객체의 내부의 값이 같은지 숫자로 확인하는 값은 hashcode()이다
    • 같은 객체인지 확인하는 메소드는 equals()이다.
  • 추가적으로, 원래 Object.equals()도 내부적으로는 ==연산을 취하지만, String과 같은 다른 객체 타입으로 상속되면서 equals()가 오버라이딩되어 구현된 메소드이다.
profile
한단계씩 올라가는 개발자

0개의 댓글