비교
- == (동일성 identity 비교)
- 객체의 주소값 비교로 같은 메모리 공간을 가리키는지 확인
- equals (동등성 equivalence 비교 )
- 객체의 내부 값이 같은지 비교한다.
객체 지향 프로그래밍(OOP)에서 인스턴스(instance)는 해당 클래스의 구조로 컴퓨터 저장공간에서 할당된 실체를 의미한다. 여기서 클래스는 속성과 행위로 구성된 일종의 설계도이다. OOP에서 객체는 클래스와 인스턴스를 포함한 개념이다.(위키피디아)
그렇기 때문에 정확히 표현하면 인스턴스겠지만 여기서는 객체라고 하겠다.
equals()
public class Money {
int amount;
String currencyCode;
public Money(int amount,String currencyCode){
this.amount=amount;
this.currencyCode=currencyCode;
}
}
class MoneyTest {
@Test
@DisplayName("같은 객체를 equals 비교")
void equals(){
//given
Money income = new Money(55, "USD");
Money expenses = new Money(55, "USD");
//when & then
assertThat(income).isEqualTo(expenses);
}
}
income과 expenses는 amount,currencyCode가 같으므로 값이 같은 객체로 보고 있다.
그러므로 equals는 true가 나와야한다.
하지만 결과가 실패가 나왔다. 값이 아닌 주소값을 비교하고 있기 때문에 주소값이 달라 실패가 나온 것이다.
그냥 equals를 사용하면 ==인 것을 알 수 있다.
Object 클래스에 정의된 equals()
public boolean equals(Object obj) {
return (this == obj);
}
그렇기 때문에 재정의가 필요하다.
public class Money {
int amount;
String currencyCode;
public Money(int amount,String currencyCode){
this.amount=amount;
this.currencyCode=currencyCode;
}
//equals 재정의
@Override
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Money))
return false;
Money other = (Money)o;
boolean currencyCodeEquals = (this.currencyCode == null && other.currencyCode == null)
|| (this.currencyCode != null && this.currencyCode.equals(other.currencyCode));
return this.amount == other.amount && currencyCodeEquals;
}
}
income과 expenses는 주소값은 다르지만(객체는 다르지만) 같은 값으로 보고 있다.
언제 equals를 재정의 해야 할까?
- 두 값 객체를 equals로 비교한다는 것은 객체가 같은지가 아니라 값이 같은 것인지 알고 싶은 것이다.
- 상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의 되어있지 않을때
- String과 Integer 클래스는 이미 equals가 재정의 되어있기 때문에 할 필요없다.
equals 메서드를 재정의할 때는 반드시 일반 규약을 따라야한다.(Object 명세에 적힌 규약)
- 반사성(reflexivity): null이 아닌 모든 참조 값 x에 대해, x.equals(x)는 true이다.
- 대칭성(symmetry): null이 아닌 모든 참조 값 x,y에 대해, x.equals(y)는 true면 y.equals(x)는 true다.
- 추이성(transitivity): null이 아닌 모든 참조 값 x,y,z에 대해, x.equals(y)는 true면 y.equals(z)도 true면 x.equals(z)도 true다.
- 일관성(consistency): null이 아닌 모든 참조 값 x,y에 대해, x.equals(y)를 반복해서 호출하면 항상 true를 반환하거나 항상 false를 반환한다.
- null-아님: null이 아닌 모든 참조값 x에 대해, x.equals(null)는 false이다.
그렇다면 정의하지 않은 경우는?
- 논리적으로 같은 인스턴스가 2개 이상 만들어지지 않으니 논리적 동치성과 객체 식별성이 사실상 똑같은 의미가 된다.
- 값이 같은 인스턴스가 둘 이상 만들어지지 않음을 보장하는 인스턴스 통제 클래스
- Enum
논리적 동치성이란?
객체 식별성(두 객체가 물리적으로 같은가,같은 주소값을 가지는가)이 아니라 값이 같은지를 말하는 것이다.
정리
꼭 필요한 경우가 아니면 equals를 재정의하지 말자.
재정의 할 때는 그 클래스의 핵심 필드 모두를 빠짐없이 다섯 가지 규약을 지켜가며 비교해야한다.
hashCode()
equals()를 재정의했다면 반드시 hashcode()도 재정의해야한다. 그렇지 않으면 hashcode의 일반 규약을 어기게 되어 해당 클래스의 인스턴스를 HashMap이나 HashSet 같은 컬렉션의 원소로 사용할 때 문제를 일으키게 된다.
…If two objects are equal according to the equals(Object) method, then calling the hashCode method on each of the two objects must produce the same integer result.…
논리적으로 같은 객체는 반드시 같은 해시코드를 반환해야한다.
Object의 hashCode()
@HotSpotIntrinsicCandidate
public native int hashCode();
-객체의 해시코드를 반환하는 메서드(해시코드는 정수값으로 해싱 알고리즘에서 사용)
-Object 클래스의 hashCode()는 객체의 주소를 int로 변환해서 반환
-객체의 주소를 int로 반환하기 때문에 Object클래스의 hashCode()는 객체 값이 같더라도 다르다.
public class Menu {
private final String name;
private final int price;
public Menu(final String name, final int price) {
this.name = name;
this.price = price;
}
}
Menu menu1=new Menu("name1",100);
Menu menu2=new Menu("name1",100);
System.out.println(menu1.hashCode()); //659748578
System.out.println(menu2.hashCode()); //240650537
-그렇지만 오버라이딩해서 객체 값이 같은 equals()의 결과가 true인 두 객체의 해시코드는 같아야 하기 때문에 equals()를 오버라이딩하면, hashCode()도 오버라이딩해야 한다.(아래 코드는 String의 hashCode로 오버라이딩이 잘 되어있다)
String str1= new String("abc");
String str2= new String("abc");
System.out.println(str1.equals(str2)); //true
System.out.println(str1.hashCode()); //96354
System.out.println(str2.hashCode()); //96354
-주소 범위가 int이기 때문에 겹칠 수 있다.
equals()와 hashCode() 구현하는 방법
IDE(여기서는 intellij)에서 equals()와 hashCode()를 구현하게끔 제공해준다.
필요한 클래스에서 우클릭→Generate→equals() and hashCode()
위에서 쓰인 예제 Money.class에 적용한 결과
public class Money {
int amount;
String currencyCode;
public Money(int amount,String currencyCode){
this.amount=amount;
this.currencyCode=currencyCode;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Money money = (Money) o;
return amount == money.amount && Objects.equals(currencyCode, money.currencyCode);
}
@Override
public int hashCode() {
return Objects.hash(amount, currencyCode);
}
}
참고
도서 : 이펙티브 자바 Effective Java 3/E 의 아이템 10. equals는 일반 규약을 지켜 재정의하라
https://velog.io/@sonypark/Java-equals-hascode-메서드는-언제-재정의해야-할까
https://www.baeldung.com/java-equals-hashcode-contracts#2-equals-contract
'Backend > Java' 카테고리의 다른 글
[Java] Static (0) | 2024.02.22 |
---|---|
직렬화와 역직렬화 (0) | 2024.01.26 |
[StringUtills] String의 null / 빈 값 / 공백 처리하기 (0) | 2023.02.27 |
[Jackson] Json 날짜 타입 매핑하기 (0) | 2022.02.10 |
Hash Map / Hash Table / Tree Map (0) | 2021.06.30 |