들어가며
우테코 사다리타기 미션을 진행하던 중에 값을 비교하기 위해 equals
를 사용했다.
equals
는 단지 값을 비교하기 위해 사용하면 되지 않나 ?' 라는 생각을 가지고 있던 나는 잘못 생각하고 있었다. 그래서 부끄러워서 정리 한번 해보려고 한다.
문제가 되었던 코드 중 하나
private void validateWinner(Names names, String winner) {
if (!(names.nameContains(new Name(winner))) {
throw new IllegalArgumentException(ExceptionMessage.EXCEPTION_WINNER_RESULT.getExceptionMessage());
}
}
names내에서 new Name(winner) 로 이루어진 객체가 있는지 여부를 확인하는 코드였다. 하지만 계속해서 에러가 발생했다.
에러의 발생 이유는 다음과 같다.
new Name(”ocean”)
과 new Name("ocean")
winner 값이 같은 Name 객체 두개가 존재한다면 같다고 판단해야 될 것이다. 하지만 이는 두개의 인스턴스 객체로써 레퍼런스가 다르다. 즉 우리가 평소에 쓰던 equals 메소드는 주소값이 다른 객체는 서로 다른 객체로 판단하게 된다. 그래서 equals를 원래 의도대로 사용하고자 한다면 equals 메소드를 재 정의해줄 필요가 있다.
equals 재정의
equals 재정의를 통해서 equals를 == 이 아닌 객체의 값을 비교해 줄 수 있도록 재정의 해준다.
재정의를 해주지 않는다면 원시값과 같은 경우에는 내용을 비교하고 객체의 경우에는 레퍼런스 값을 비교해주기 때문이다.
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Name name = (Name) o;
return Objects.equals(this.name, name.name);
}
Name
객체에서 equals
를 재 정의하였다.
이렇게 새롭게 정의를 한다면 전과 다르게 equals의 값이 제대로 작동하는 것을 확인할 수 있다!
언제 이렇게 equals를 재정의 해서 사용할까?
주소가 같은 경우이거나 논리적으로 인스턴스들이 동일하다고 판단된다면 equals를 재정의하여 사용해야한다. 동명이인이 없다고 했을 때 new Name("ocean")
과 new Name("ocean")
는 동일한 인물이다. 이럴 경우 가리키는 인스턴스는 달라도 논리적으로 맞기 때문에 equals를 재정의하여 같다고 판단할 수 있도록 수정할 필요가 있다.
equals 재정의 는 어떻게 하면 좋을까 ?
위에 코드에서 본 equals 재정의 문도 나름의 구현 방법이 있다.
- 객체는 자기 자신과 같아야한다.
- 서로에 대한 동치 여부에 같게 대답해야한다.
- 삼단 논법이 일치한다.
x.equals(y)
가true
이고y.equals(z)
도true
이면x.equals(z)
도true
hashCode 재정의
이번 사다리 미션에서는 사다리 게임의 결과를 hashMap에 저장하였다. 그러면서 외부 객체와 비교를 하여 결과값을 출력하도록 구현하려 하였지만 실패하였다. 아래는 내가 만든 Map
이다.
private Map<Name, Goal> prizeResult = new HashMap<>();
나는 Map
에 있는 Name
중 Ocean과 같은 사람을 찾고 싶은데 계속해서 실패하였다. 이는 해쉬값을 비교해야 되기 때문이다.
Set<Name> names= new HashSet<>();
names.add(new Name("ocean"));
names.add(new Name("ocean"));
나는 위와 같은 코드가 있다면 set
에는 논리적으로 같은 Name
객체가 두개가 들어가 있으므로 하나의 값만 들어가면 된다고 생각하였다. 하지만 두개의 객체가 들어가 있었는데 이는 각 객체의 해시값이 달라서 생긴 문제이다.
이는 hash값을 사용하는 Collection(HashSet, HashMap, HashTable)을 사용할 때 문제가 발생한다.
왜 hash 에서만 문제가 발생하는가 ?
hash값을 사용하는 Collection에서 객체를 비교하기 위해서는 hash
값과 equals
값을 모두 비교해야한다. 둘중 하나라도 다르다면 다른 객체로 판별한다.
위에서 우리는 equals
가 객체의 레퍼런스를 반환한다는 이유로 재정의 하였다. 그렇게 함으로써 제대로 된 값을 반환할 수 있도록 만들었다.
hashCode
메소드는 객체의 고유hash값을 반환한다. 그렇기 때문에 값이 같은 객체이더라도 다른 값을 반환한다. 이것을 재정의를 통해서 해결하여야한다.
@Override
public int hashCode() {
return Objects.hash(name);
}
이렇게 equals
와 함께 hashCode
를 정의하면 된다.
hashCode 재정의는 어떻게 하면 좋을까 ?
- int 형 값에 equals 비교에 사용되는 핵심필드의 hashCode값으로 초기화 하여 반환한다.
equals와 hashCode 무조건 같이 재정의 해야하나 ?
hashCode 같은 경우에는 hash 값 사용하는 Collection때문에 재정의를 한다고 말하였다. 그래서 상황별로 같이 재정의 할 필요가 없다고 생각할 수 있다.
하지만 ! hash 자료구조에서는 hash로 비교 후 equals로 추가 비교 후 비교값 판단을 한다. 그러므로 hash 자료구조에서는 equals
와 hashCode
모두 재정의가 필요한 것이다.
😒 여기서 어라 ~ 저는 hash 죽어도!죽어도! 안쓸건데요 ?
라고 한다면 꼭 hashCode를 오버라이딩할 필요는 없다. 하지만 요구사항이 변동되어 hash를 사용할 수 있고 협업 과정에서 문제가 생길 가능성도 존재하기 때문에 거의 같이 재정의해주면 좋다.
나가면서
사다리 타기 게임을 하면서 Map의 Key를 내가 포장해놓은 값으로 해놓고 비교를 위해 사용하려고 하였다. 당연히 equals, hashCode 오버라이딩은 안해놓은 상태여서 에러가 발생하였고 계속해서 고민하였다. 이런 실수를 앞으로는 안하려 한다.
'코코코딩공부 > JAVA' 카테고리의 다른 글
[JAVA] 일급 컬렉션 (0) | 2023.02.25 |
---|---|
[JAVA] 원시값 포장 (0) | 2023.02.25 |
[JAVA] 정규표현식 (0) | 2023.02.14 |
[JAVA] static, final, static final 이란 ? (0) | 2023.02.12 |
[JAVA] ENUM (0) | 2023.02.06 |