Object는 객체를 만들 수 있는 구체 클래스지만 기본적으로는 상속해서 사용하도록 설계되어있다. Object 에서 final이 아닌 메서드(equals, hashCode, toString, clone, finalize)는 모두 재정의를 염두에 두고 설계 된 것이라 재정의 시 지켜야하는 일반 규약이 정의도어 있다. 모든 클래스는 이 메서드들을 일반 규약에 맞게 재정의 해야한다.
equals는 일반 규약을 지켜 재정의하라
다읨에 열거한 상황 중 하나에 해당한다면 재정의하지 않는 것이 최선이다.
각 인스턴스가 본질적으로 고유하다.
인스턴스의 논리적 동치성을 검사할 일이 없다.
상위클래스에서 재정의한 equals가 하위클래스에도 딱 들어 맞는다.
클래스가 private이거나 package-private이고 equals 메서드를 호출할 일이 없다.
재정의 해야하는 경우는 논리적 동치성을 확인해야하는데 상위 클래스의 equals가 논리적 동치성을 비교하도록 재정의되지 않았을 경우이며, 값 클래스들이 여기에 해당한다. 값 클래스란 Integer와 String처럼 값을 표현하는 클래스를 발한다.
equals 메서드 규약
equals 메서드는 동치관계를 구현하며 다음을 만족한다.
반사성 : null이 아닌 모든 참조값에 대해 x.equals(x)는 true이다.
대칭성 : null이 아닌 모든 참조값 x,y에 대해 x.equals(y)가 true이면 y.equals(x)도 true이다.
추이성 : null이 아닌 모든 참조값 x,y,z 에대해 x.equals(y) true이고 y.equals(z)가 true이면 z.equals(x)도 true이다.
일관성 : null이 아닌 모든 참조값 x,y에 대하여 x.equals(y)를 반족해서 호출하면 항상 true이거나 false를 반환한다.
null-아님 : null이 아닌 모든 참고값 x에 대하여 x.equals(null)은 false이다.
구체 클래스를 확장해 새로운 값을 추가하며 equals규약을 만족시킬 방법은 존재하지 않는다.
리스코프 치환 원칙 : 어떤 타입에 있는 중요한 속성이라면 그 하위 타입에서도 독같이 잘 작동해야한다.(다형성 가능해야 한다)
이를 위해서 는 equals를 instanceof 기반으로 구현해야한다.
(instanceof 를 이용하여 매개변수가 올바른 타입인지 검사하여 null-아님에 대해서도 체크 할 수 있다.)
java.sql.Timestamp는 equals의 대칭성을 위배한다. 잘못된 코드이기에 따라해서는 안된다.
클래스가 불변이든 가변이든 equals의 판단에 신뢰할 수 없는 자원이 기어들어가서는 안된다. java.net.URL역시 일관성을 어기기에 따라해서는 안된다.
equals는 항시 메모리에 존재하는 객체을 사용한 결정적 계산만 수행해야한다.
구현 단계
- == 연산자를 사용해 입력이 자기 자신의 참조인지 확인한다.
- instaceof 연산자로 입력이 올바른 타입인지 확인한다.
- 입력을 올바른 타입으로 형변환한다.
- 입력 객체와 자기 자신의 대응되는 '핵심' 필드들이 모두 일치하는지 하나씩 검사한다.
- equals구현후 대칭적, 추이성, 일관적인지 테스트한다.
float와 double을 제외한 기본 타입 필드는 ==으로 비교하며 참조타입의 필드는 각각의 equals 메서드로 비교한다. float과 double의 경우 특수한 부동소수값,k Float.NaN, -0.0f 등을 다뤄야한다. Float.equals와 Double.equals를 대신 사용할 수도 있지만 오토박싱을 수반하니 성능에 좋지 않다. 배열의 경우 모든 원소가 핵심필드라면 Arrays.equals를 사용한다.
null을 정상 값으로 취급하는 참조타입 필드는 Objects.equals(Object,Object)로 비교해 NullPointException 발생을 에방한다.
어떠한 필드를 먼저 비교하는지가 성능에 영향을 미친다. 성능상 최상을 원한다면 다를 가능성이 더 크거나 비교하는 비용이 씬 필드를 먼저 비교하는 것이 좋다. 동기화용 락 필드 같이 객체의 논리적 상태와 관련 없는 필드는 비교해서는 안된다. 파생필드 역시 비교할 필요는 없지만 상황에 따라 파생필드를 비교하는 것이 빠를 때도 있다.
equals를 재정의 할땐 hashCode도 반드시 재정의하자
필드들의 동치성만 검사해도 equals 규약을 어렵지 않게 지킬 수 있다.
Object외 타입을 매개 변수로 받는 equals 메서드는 선언하지 말자. 매개변수는 반드시 Object 타입이여야 하며 다른 타입인 경우 재정의가 아닌 다중정의에 속한다. 이는 어노태이션 사용을 통해 예방 할 수 있다.
equals를 재정의 할땐 hashCode도 반드시 재정의하자
equals를 재정의한 모든 클래스에서 hashCode또한 재정의 하지 않으면 hashCode의 일반 규약을 어기게 된다.
equals(Object)가 두 객체를 같다고 판단 했다면, 두 객체의 hashCode는 똑같은 값을 반환해야 한다.
성능을 위해서 해시코드를 계산할 때 핵심필드를 생략해서는 안된다.
hashCode가 반환하는 값의 생성 규칙을 API 사용자에게 자세히 공표하지 않아야 클라이언트가 이 값에 의지하지 않게되며, 추후에 계산식을 바꿀 수 있다.
toString을 항상 재정의하라
toString을 잘 구현한 클래스는 사용과 디버깅을 쉽게 만든다.
toString은 그 객체가 가진 주요 정보를 모두 반환하도록 하는 것이 좋다.
toString을 구현할 때 반환값의 포맷을 문서화할지 정해야한다.
정적팩토리나 생성자를 함께 제공해주는 것이 좋다.
포맷을 명시하든 아니든 의도는 명확히 밝혀야한다.
toString이 반환한 값에 포함된 정보를 얻어올 수 있는 API를 제공하자.
clone 재정의는 주의해서 진행하라
Object의 protected메서드인 clone의 동작 방식을 결정한다.
원본이나 복제본중 하나를 수정하면 다른 하나도 수정되어 불벽식을 해치게 된다.
clone메서드는 사실상 생성자와 같은 효과를 낸다. 즉, clone은 원본 객체에 아무런 해를 끼치지 않는 동시에 복제된 객체의 불변식을 보장해야한다.
배열의 clone은 런타임타입과 컴파일 타입 모두가 원본 배열과 똑같은 배열을 반환한다. 따라서 배열 복제시 clone메서드를 사용하는 것을 권장한다. 하지만 clone()보다는 deepCopy()를 쓰는 것이 권장된다.
Object의 clone메서드는 CloneNotSupportedException을 던진다고 선언했지만 재정의한 메서드는 그렇지 않다. public인 clone메서드에서는 throws절을 없애야 한다.
Cloneable을 구현한 모든 스레드 안전 클래스는 clone을 재정의해야한다. 접근 제한자는 public, 반환타입은 클래스 자신으로 변경한다.
새로운 인터페이스 작성시 Cloneable을 확장해서는 안되며 새로운 클래스도 이를 구현해서도 안된다 final은 위험이 크지 않지만 성능적인 관점에서 검토후 사용해야한다. 복제 기능은 생성자와 팩토리를 이용하는 것이 좋으나 배열 만은 예외이다.
Comparable을 구현할지 고려하라
compareTo는 단순 동치성비교에 더해 순서까지 비교할 수 잇으며 제네릭하다. null과 ""비교시 equals는 false이지만 compareTo는 0이 나오수도 있다.
Comparable을 구현한 클래스는 반드시 추이성을 보장해야한다.
compareTo 규약
- 두 객체 참조의 순서를 바꿔 비교해도 예상한 결과가 나와야한다.
- 첫번째가 두번째보다 크고 두번째가 세번째보다 크면 세번째는 첫번째보다 커야한다.
- 크기가 같은 객체들은 어떤 객체와 비교해도 항상 같아야한다.
- 동치성 테스트의 결과가 equals와 같아야 한다.(필수는 아니다)
TreeSet같이 정렬된 컬렉션들은 동치성을 비교할때 equals다신 compareTo를 사용한다. HashSet은 원소를 2개 갖는다. TreeSet을 사용하면 원소를 하나만 갖게 된다.
Comparable은 타입을 인수로 받는 제네릭 인터페이스이므로 compareTo 메서드이 인수타입은 컴파일 타임에 정해진다.
compareTo 메서드는 각 필드가 동치인지를 비교하는게 아니라 그 순서를 비교한다. compareTo 메서드에서 관계연산자 <와>를 사용하는 이전방식은 사용을 권장하지 않는다.
'IT > Java' 카테고리의 다른 글
이펙티브 자바 4장 -2 (0) | 2019.12.02 |
---|---|
이펙티브 자바 4장 - 1 (0) | 2019.11.27 |
이펙티브자바 2장 (0) | 2019.11.23 |
1장 이펙티브 자바 (0) | 2019.11.22 |
null의 활용 (0) | 2019.07.29 |
댓글