Item21. 인터페이스는 구현하는 쪽을 생각해 설계해라
default method
java 8 이후부터는 interface에 default method를 추가할 수 있게 되었다.
default method 를 기존 인터페이스에 추가하였을떄 다음과 같은 사이드 이펙트가 발생할 수 있으니, 고려해서 추가하자는 내용이다.
java 8 이후부터는 interface에 default method를 추가할 수 있게 되었다.
default method 를 기존 인터페이스에 추가하였을떄 다음과 같은 사이드 이펙트가 발생할 수 있으니, 고려해서 추가하자는 내용이다.
추상 class와 인터페이스의 가장 큰 차이점은 추상 class가 정의한 type을 구현하는 class는 반드시 추상 class의 하위 타입이 되야한다는 점이다.
java는 단일 상속만 지원함으로, 추상 class 방식은 새로운 타입을 정의하는데 제약을 갖게 된다. 반면 인터페이스의 경우는 어떤 class를 상속했더라도 같은 type으로 취급된다
기존 class에 새로운 인터페이스를 구현해넣는것은 쉬우나, 새로운 추상 class를 넣기는 힘들다.
두 class가 동일한 추상 class 를 확장하려면 그 추상 class는 계층구조상 두 class의 공통조상이여야 한다.
문서화 예시 : https://docs.oracle.com/javase/7/docs/api/java/util/AbstractCollection.html
API 문서의 method설명 끝에서 종종 ‘This Implementation’ ~ 로 시작하는 절을 볼 수 있는데 이 부분이 method의 내부 동작 방식을 설명하는 곳이다.
클래스를 안전하게 상속할 수 있도록 하려면, 내부 구현 방식에 대해 설명해주어야 한다.
( Java method 주석에 @implSpec tag를 붙여주면 자바독 도구가 생성해준다. )
상위 class 내부 구현 변경에 따라 하위 class가 영향을 받을 수도 있다. 상위 class가 확장을 충분히 고려해두고 설계되지 않으면 하위 class는 상위 class의 변화맞추어 수정되야 한다.
다음과 같이 캡슐화되지 않은 class는 필드를 외부에서 직접 접근이 바로 가능하니 작성하지 않는 것이 좋다. (모듈간 독립성이 떨어짐)
1 | public class Point { |
불변 클래스란 객체 생성 후에 객체의 내부 값을 수정할 수 없는 클래스이다. 불변 객체에 간직된 정보는 고정되어 객체 소멸 시점까지 변경되면 안된다.
자바 String,primitive type boxing 클래스, BigInteger,BigDecimal 이 여기에 속한다.
불변 객체를 사용하는 이유는 다음과 같다.
객체의 상태를 변경하는 메소드 (ex) setter) 를 제공하지 않는다
class를 확장할 수 없도록 한다. 대표적으로 class를 final로 선언하면 된다.
모든 필드를 final로 선언한다.
( 어떤 method도 객체의 상태 중 외부에 비치는 값을 변경할 수 없다. )
모든 필드를 private로 선언한다. 불변 필드인 경우에는 public final로 선언하여도 불변 객체는 되지만, API를 수정하기 힘들어진다.
자신 외에는 내부의 가변 컴포넌트에 접근할 수 없도록 한다. 클래스 내부에 가변 필드가 있다면, client에서 그 가변 필드의 참조값을 가지고 수정할수 있으므로 생성자/접근자/readObject method에서 모두 방어적 복사를 수행해야 한다.
잘 설계된 컴포넌트는 모든 내부 구현을 완벽히 숨겨, 구현과 API를 깔끔하게 분리한다.
오직 API를 통해서만 다른 컴포넌트와 소통하며, 서로 구체적인 동작 방식은 개의치 않는다.
시스템을 구성하는 컴포넌트를 서로 독립시켜서 개발/테스트/적용/수정 분석을 개별적으로 할 수 있게 해준다
여러 컴포넌트를 병렬로 개발 가능함으로, 개발 속도를 높임
각 컴포넌트를 빨리 파악 및 디버깅이 관리하고, 다른 컴포넌트로 교체하는 작업이 손쉽기 때문에 유지보수 비용이 덜 든다
직접적으로 성능을 높여주지는 않지만, 성능최적화에 도움을 준다. 완성된 시스템을 프로파일링해 최적화할 컴포넌트를 정하고, 해당 컴포넌트만 최적할 수 있기 때문이다.
SW 재사용을 높임
시스템 개발 난이도를 낮춘다. 개별 컴포넌트단위로 나누어 개발하고 테스트할 수 있기 때문이다.
compareTo는 단순 동치성 비교에 더해 순서까지 비교할 수 있으며, generic하다.
1 | public interface Comparable<T> { |
comparable을 구현했다는 것은 그 클래스의 인스턴스들에 순서가 있음을 뜻한다. 그래서 Comparable을 구현한 객체들의 배열은 다음과 같이 정렬이 가능하다.
1 |
|
순서가 명확한 값 class를 작성한다면 Comparable 인터페이스 구현을 고려하자.
compareTo method의 일반 규약은 다음과 같다.
compareTo method는 주어진 객체와 this 객체의 순서를 비교한다. this 객체가 주어진 객체보다 작으면 음의 정수 , 같으면 0 , 크면 양의 정수를 반환하고 이 객체와 비교할수 없는 타입의 객체가 주어지면 ClassCastException을 던진다. 1. Comparable을 구현한 class는 모든 x,y에 대해서 sgn(x.compareTo(y)) == -sgn(y.compareTo(x)) 2. Comparable을 구현한 class는 추이성을 보장해야 한다. ( x.compareTo(y) > 0 && y.compareTo(z) > 0)이면 x.compareTo(z) > 0이다. 3. Comparable을 구현한 class는 모든 z에 대해 x.compareTo(y) == 0 이면, sgn(x.compareTo(z)) == sgn(y.compraeTo(z)) 다. 4. 부가적으로 다음의 규약은 필수는 아니나 지키면 좋다.(x.compareTo(y) == 0) == x.equals(y) 여야 한다. 만약 위 식이 성립하지 않는다면 이 클래스의 순서는 equals method와 일관되지 않음을 명시해야 한다.
compareTo규약을 지키지 못하면 비교를 활용하는 TreeSet, TreeMap, Collections,Array등의 클래스에서 제공해주는 API가 정상적으로 작동하지 않을 수도 있다.
Object class에는 clone() method가 protected로 선언되어 있고, 객체를 복사하려면 java.langCloneable Interface를 implement하여 복사할 수 있다.
1 | package java.lang; |
(https://docs.oracle.com/javase/7/docs/api/java/lang/Cloneable.html)
Cloneable은 아무 method도 없는 marker interface지만 ,Object의 clone method의 동작 방식을 결정한다.
Cloeanble을 구현한 class에서부터 만들어진 객체에서 clone을 호출하면 객체의 필드들을 복사한 객체를 반환하도록 하고, 그렇지 않은 객체에서는 CloneNotSupportedException을 던진다.
Object toString 은 별도로 overrriding 하지 않는 경우에는 class명@16진수_해시코드 를 반환한다.
1 | getClass().getName() + '@' + Integer.toHexString(hashCode()) |
Object 명세에 보면 toString은 간결하며, 사람이 읽기 쉬운 형태의 정보를 반환해야 하며,
toString을 overrding한 모든 하위 클래스에서도 toString 을 재정의해야 하며, 해당 객체가 가진 주요 값 필드를 반환하는게 바람직하다.
(https://docs.oracle.com/javase/7/docs/api/java/lang/Object.html)
toString을 재정의하면 다음과 같은 장점을 가지고 있다.
선택사항 : toString 사용시 포맷을 문서화 여부