관리 메뉴

The Nirsa Way

[Effective Java 3/E - 모든 객체의 공통 메서드] Item 10-2. equals는 일반 규약을 지켜 재정의하라 - equals를 재정의해야 하는 상황과 인스터스 통제 클래스 본문

Development/JAVA

[Effective Java 3/E - 모든 객체의 공통 메서드] Item 10-2. equals는 일반 규약을 지켜 재정의하라 - equals를 재정의해야 하는 상황과 인스터스 통제 클래스

KoreaNirsa 2025. 7. 14. 16:42
반응형

 

equals는 일반 규약을 지켜 재정의하라 - equals를 재정의해야 하는 상황과 인스턴스 통제 클래스

주로 Value Object들이 equals()를 재정의할 상황을 가집니다. Object의 equals()는 기본적으로 참조 동일성 비교(==)를 사용하게 되는데 값을 주로 저장하는 Value Object의 경우 논리적 동치성(값이 동일한가)을 검사할 상황이 발생합니다. 즉 "같은 값을 가지고 있는가?"를 판단해야 한다면 equals()를 재정의할 필요가 있을 것 입니다.

아래의 코드는 금액(amount)과 통화(currency) 값을 저장하는 Value Object 입니다. equals()를 재정의하여 값이 일치하는지 비교하는 로직이 추가된 상태입니다.

public class Money {
    private final int amount;
    private final String currency;

    public Money(int amount, String currency) {
        this.amount = amount;
        this.currency = currency;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (!(obj instanceof Money)) return false;
        Money other = (Money) obj;
        return this.amount == other.amount &&
               this.currency.equals(other.currency);
    }
}

아래와 같이 사용하게 되면 m1 == m2를 할 경우 서로 다른 인스턴스이므로 false가 발생하고, 재정의한 equals를 사용하면 값 자체를 비교(논리적 동치성 검사) 하므로 값이 일치하기 때문에 true가 발생합니다. 만약, equals()를 재정의하지 않았다면 서로 다른 인스턴스이므로 false가 발생했을 것 입니다.

이와 같이 값을 비교해야하는 경우 equals()를 재정의하여 해당 로직에 맞게 수정하여 사용하는 것이 좋습니다.

public class Test {
    public static void main(String[] args) {
        Money m1 = new Money(1000, "KRW");
        Money m2 = new Money(1000, "KRW");

        System.out.println(m1 == m2);        // false (참조 다름)
        System.out.println(m1.equals(m2));   // true  (값이 같음 → 논리적 동치성)
    }
}

 

하지만 예외인 케이스가 있는데, Value Object더라도 값이 같은 인스턴스가 둘 이상 만들어지지 않음을 보장하는 인스턴스 통제 클래스(instance-controlled class)라면 equals()를 재정의하지 않아도 됩니다. 

※ 인스턴스 통제 클래스(instance-controlled class) 란?
같은 값을 가진 인스턴스는 하나만 존재하도록 하며 내부적으로 인스턴스를 공유하거나 캐싱하여 통제하는 클래스
참고 : [Effective Java 3/E - 객체 생성과 파괴] Item 1. 생성자 대신 정적 팩터리 메서드를 고려하라

아래는 인스턴스 통제 클래스의 예시입니다. 해당 코드는 생성자를 private으로 작성하여 외부에서의 인스턴스 생성을 제한하고, 정적 팩토리 메서드로만 통제하게 됩니다.  

public final class MyBoolean {
    private static final MyBoolean TRUE = new MyBoolean(true);
    private static final MyBoolean FALSE = new MyBoolean(false);

    private final boolean value;

    private MyBoolean(boolean value) {
        this.value = value;
    }

    public static MyBoolean valueOf(boolean b) {
        return b ? TRUE : FALSE;
    }

    public boolean booleanValue() {
        return value;
    }

    // equals 재정의 필요 없음!
}

이러한 인스턴스 통제 클래스의 경우 valueOf()를 호출하더라도 항상 같은 인스턴스를 반환하므로 굳이 equals()를 재정의하여 값이 일치하는지 비교할 필요가 없으며 참조 동일성 비교(==)만으로도 충분합니다. Object의 equals는 기본적으로 참조 동일성 비교(==)를 사용하므로 같은 의미이기 때문에 ==와 같은 결과가 반환됩니다.

public class Test {
    public static void main(String[] args) {
        MyBoolean b1 = MyBoolean.valueOf(true);
        MyBoolean b2 = MyBoolean.valueOf(true);

        System.out.println(b1 == b2);      // true (같은 인스턴스)
        System.out.println(b1.equals(b2)); // true (Object의 기본 equals도 true → 동일 참조)
    }
}

 

 

 

 

 

 

 

 

반응형