ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • [Java] 이펙티브 자바 (2) - 모든 객체의 공통 메서드
    Java 2020. 4. 18. 01:13
    반응형

    아이템 10. equals는 일반 규약을 지켜 재정의하라

    equals를 재정의하지 않아야 할 때(하나라도 해당한다면)

    - 각 인스턴스가 본질적으로 고유

    - 인스턴스의 '논리적 동치성(logical equality)'을 검사할 일이 없음

    - 상위 클래스에서 재정의한 equals가 하위 클래스도 딱 맞는 경우의 하위 클래스

    - 클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없음

    실수로라도 equals가 호출되는 것을 막고 싶다면?

     equals를 override하되, AssertionError를 던지도록 구현해두기

    equals를 재정의해야 할 때

    - 객체 식별성(object identity)이 아닌 논리적 동치성을 확인해야 하는데, 상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의되지 않았을 때

    - 주로 값 클래스들이 위에 해당

        - 값 클래스라해도, 인스턴스 통제 클래스라면 equals를 재정의하지 않아도 됨 - Enum도 여기에 해당

    equals 메서드를 재정의할 때의 지켜야 할 일반 규약

     equals 메서드는 동치관계(equivalence relation)를 구현하며, 다음을 만족시켜야 함 - 어길 경우 그 객체를 사용하는 다른 객체들이 어떻게 반응하는지 알 수 없음

    - 반사성(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

    양질의 equals 메서드 구현 방법 단계

    1. == 연산자로 입력이 자기 자신의 참조인지 확인

    2. instanceof 연산자로 입력이 올바른 타입인지 확인

    3. 입력을 올바른 타입으로 형변환

    4. 입력 객체와 자신의 대응되는 '핵심' 필드들이 모두 일치하는지 하나씩 검사

    참고사항

    - equals를 재정의할 경우 hashCode도 반드시 재정의

    - 일반적으로 별칭(alias)은 비교하지 않는 것이 좋음

    - Object 외의 타입을 매개변수로 받는 equals 메서드는 선언하지 말 것

    용어

     Logical Equality(논리적 동치성) : p ≡ q. 두 (합성)명제 p와 q의 진릿값이 서로 같은 경우

     Object Identity(객체 식별성) : 두 객체가 물리적으로 같은가

     Equivalence relation(동치관계) : 논리적 동치와 비슷한 성질들을 만족시키는 이항 관계


    아이템 11. equals를 재정의하려거든 hashCode도 재정의하라

    equals를 재정의한 클래스 모두에서 hashCode도 재정의 해야 함

    - equals 비교에 사용되는 정보가 변경되지 않았다면, 애플리케이션이 실행되는 동안 그 객체의 hashCode 메서드는 몇 번을 호출해도 일관되게 항상 같은 값을 반환해야 함. 단, 애플리케이션을 재 실행하는 경우, 이 값이 달라져도 상관 없음

    - equals(Object)가 두 객체를 같다고 판단했다면, 두 객체의 hashCode는 같은 값을 반환해야 함

    - equals(Obejct)가 두 객체를 다르다고 판단했더라도, 두 객체의 hashCode가 서로 다른 값을 반환할 필요는 없음. 하지만, 다른 객체의 경우 다른 값을 반환해야 해시테이블의 성능이 좋아짐

    hashCode 재정의를 잘못한다면?

     HashCode를 이용한 API들이 의도와 다르게 동작할 것(예 - HashMap)


    아이템 12. toString을 항상 재정의하라

    toString의 규약

     "모든 하위 클래스에서 해당 메서드를 재정의하라"

    재정의한 toString

     사용하기 좋고, 디버깅하기 쉬움

    어떻게 재정의를 해야할까

     그 객체가 가진 주요 정보 모두를 반환하는 것이 좋음

     포맷을 명시하든 아니든 의도를 명확히 밝혀야 함(주석)

     기능의 경우 toString의 내용을 이용하지 말고, 필요한 값을 얻어올 수 있는 API(메서드)를 따로 제공할 것!


    아이템 13. clone 재정의는 주의해서 진행하라

    Cloneable Interface

     복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스(mixin interface)

     구현시 Object의 clone 메서드를 사용할 수 있게 됨 - 필드를 하나하나 복사한 객체 반환

     미구현시 CloneNotSupportedException 발생

    clone 메서드

     사실상 생성자와 동등한 효과

     원본 객체에 아무런 해를 끼치지 않는 동시에, 복제된 객체의 불변식을 보장해야 함

     참조형 변수의 경우, 깊은 복사를 해주어야 함! (꼭)

     재정의한 메서드는 throws절을 없애야 함 - 검사 예외를 던지지 않아야 사용하기 편하기 때문

     쓰레드 안전 클래스를 작성할 때는, 적절히 동기화도 해야 함

    상속용 클래스는 Cloneable을 구현하면 안됨

     - 구현해두고 하위 클래스에서 Cloneable 구현 여부를 선택하게 하던지(Object.clone()처럼)

     - clone을 퇴화시켜 두던지(final 메서드로 CloneNotSupportedException을 던지게끔)

    권장

     복제 기능은 생성자와 팩터리를 이용할 것 - (예 - new ArrayList<>(복사할List))

     단, 예외적으로 배열만은 clone 메서드 방식이 가장 깔끔함

     final 클래스라면 Cloneable을 구현해도 위험이 크지 않지만, 성능 최적화 관점에서 검토한 후 별다른 문제가 없을 경우에만 드물게 허용할 것


    아이템 14. Comparable을 구현할지 고려하라

    Comparable 인터페이스의 유일 메서드 - compareTo

     단순 동치성 비교에 더해 순서까지 비교할 수 있으며, 제네릭함

    CompareTo 메서드의 일반 규약

     이 객체와 주어진 객체의 순서를 비교. 이 객체가 주어진 객체보다 작으면 음의 정수, 같으면 0, 크면 양의 정수를 반환. 비교할 수 없는 타입의 경우 ClassCastException을 던짐

     다음 설명에서 sgn(표현식) 표기는 수학에서 말하는 부호 함수(signum function)을 뜻하며, 표현식의 값이 음수, 0, 양수일 때 -1, 0, 1을 반환하도록 정의함

     - Comparable을 구현한 클래스는 모든 x, y에 대해 sgn(x.compareTo(y)) == -sgn(y.compareTo(x))여야 함. (x.compareTo(y)가 예외를 던진다면 y.compareTo(x)도 예외를 던져야 함)

     - Comparable을 구현한 클래스는 추이성을 보장해야 함. 즉, (x.compareTo(y) > 0 && y.compareTo(z) > 0)이라면, x.compareTo(z) > 0임

     - Comparable을 구현한 클래스는 모든 z에 대해 x.compareTo(y) == 0이면 sgn(x.compareTo(z)) == sgn(y.compareTo(z))임

     - 필수가 아닌 권장이지만, 꼭 지키는 것이 좋음. (x.compareTo(y) == 0) == (x.equals(y))여야 함. 지키지 않는 모든 클래스는 해당 사실을 명시해야 함(주의 : 이 클래스의 순서는 equals 메서드와 일관되지 않음)

    구현시 유의할 점 - 정수 비교

     - <, >를 사용하는 방식은 거추장스럽고 오류를 유발하므로 박싱된 기본 타입 클래스의 정적 메서드인 compare를 이용하여 비교할 것

     - 값의 차(-)를 기준으로 반환하는 방식은 사용하지 말 것 : 정수 오버플로를 일으키거나, 부동소수점 계산 방식에 따른 오류를 낼 수 있음

    반응형

    댓글

Designed by Tistory.