반응형

코드리뷰를 할 때 배열을 사용할 것인가 리스트를 사용할 것인가는 의외로 자주 등장하는 주제이다.

배열과 리스트의 차이점을 설명하기 전에 우선 배열과 제너릭 타입의 중요한 차이를 알아야 한다. 

배열은 Sub가 Super의 하위 타입이라면 Sub 배열은 Super배열의 하위 타입이 된다. 이것을 공변이라고 하는데 함께 변한다는 뜻이다.

반면에 제너릭은 불공변이다. 즉, 서로 다른 타입 Type1, Type2가 있을 때, List, List은 서로 연관성이 없다. 여기서 중요한 차이가 나타나게 된다. 배열은 타입 에러가 발생하는 실수를 런타임에야 알게 되지만, 리스트를 사용하면 컴파일할 때 바로 알 수 있다. 또 다른 주요한 차이점으로 배열은 실체화가 된다. 이것은 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다는 뜻이다. 반면에 제너릭은 타입 정보가 런타임에는 소거된다. 소거는 제너릭이 지원되기 전의 레거시 코드와 제너릭 타입을 함께 사용할 수 있게 해주는 매커니즘으로, 자바 5가 제너릭으로 순조롭게 전환될 수 있도록 해줬다. 이상의 주요 차이로 인해 배열과 제너릭은 잘 어우러지지 못한다. 예컨대 배열은 제네릭 타입, 매개변수화 타입, 타입 매개변수로 사용할 수 없다. 즉, 코드를 new List[] 와 같은 식으로 작성하면 컴파일할 때 제네릭 배열 생성 오류를 일으킨다. 제네릭 배열을 만들지 못하게 막은 이유는 무엇일까? 타입 안전하지 않기 때문이다. 이를 허용한다면 컴파일러가 자동 생성한 형변환 코드에서 런타임에 ClassCastException이 발생할 수 있다. 런타임에 ClassCastException이 발생하는 일을 막아주겠다는 제네릭 타입 시스템의 취지에 어긋나는 것이다. 

배열을 제네릭으로 만들 수 없어 귀찮을 때도 있다. 예컨대 제네릭 컬렉션에서는 자신의 원소 타입을 담은 배열을 반환하는게 보통은 불가능하다. 또한 제네릭 타입과 가변인수 메서드를 함께 쓰면 해석하기 어려운 경고 메세지를 받게 된다. 가변인수 메서드를 호출할 때마다 가변인수 매개변수를 담을 배열이 하나 만들어지는데, 이때 그 배열의 원소가 실체화 불가 타입이라면 경고가 발생하는 것이다. 이 문제는 @SafeVarargs 애너테이션으로 대처할 수 있다. 배열로 형변환할 때 제네릭 배열 생성 오류나 비검사 형변환 경고가 뜨는 경우 대부분은 배열인 E[] 대신 컬렉션인 List를 사용하면 해결된다. 코드가 조금 복잡해지고 성능이 살짝 나빠질 수도 있지만, 그 대신 타입 안전성과 상호운용성은 좋아진다. 생성자에서 컬렉션을 받는 Chooser 클래스를 예로 살펴보자. 이 클래스는 컬렉션 안의 원소 중 하나를 무작위로 선택해 반환하는 choose 메서드를 제공한다. 생성자에 어떤 컬렉션을 넘기느냐에 따라 이 클래스를 주사위판, 매직 8볼, 몬테카를로 시뮬레이션용 데이터 소스 등으로 사용할 수 있다. choose 메서드를 호출할 때마다 반환된 Object를 원하는 타입으로 형변환해야 한다. 혹시나 아팁이 다른 원소가 들어 있었다면 런타임에 형변환 오류가 날 것이다. 만약 우리가 제너릭 타입을 사용할 때, 해당 타입을 T로 쓴다고 가정해보자. 이 T가 무슨 타입인지 알 수 없으니 컴파일러는 이 형변환이 런타임에도 안전한지 보장할 수 없다는 메시지가 나타나게 된다. 제네릭에서는 원소의 타입 정보가 소거되어 런타임에는 무슨 타입인지 알 수 없음을 기억하자. 그렇다면 프로그램은 동작하게 될까? 동작한다. 단지 컴파일러가 안전을 보장하지 못할 뿐이다. 코드를 작성하는 사람이 안전하다고 확신한다면 주석을 남기고 애너테이션을 달아 경고를 숨겨도 된다. 하지만 애초에 경고의 원인을 제거하는 편이 훨씬 낫다. 비검사 형변환 경고를 제거하려면 배열 대신 리스트를 쓰면 된다. 그러면 어떠한 경우에는 코드양이 조금 늘고 속도도 조금 더 느릴 수 있지만, 런타임 에러 ClassCastException을 만날 일은 없으니 훨씬 이득이다. 배열과 리스트 중 리스트를 사용해야 하는 이유를 제네릭과 연결 지어서 설명하고 있다. 여기와 연관해서 Object타입보다는 제네릭 타입을 써야 하는 이유를 몇 가지 추가해 보자. 우선 일반 클래스를 제네릭 클래스로 만드는 첫 단계는 클래스 선언에 타입 매개변수를 추가하는 일이다. 스택이 담을 원소의 타입 하나만 추가하면 된다. 이때 타입 이름으로는 보통 E를 사용한다. 그런 다음 코드에 쓰인 Object를 적절한 타입 매개변수로 바꾸고 컴파일해 보자. 여기까지 하면, 대체로 하나 이상의 오류나 경고가 발생하는데 대부분 E와 같은 실체화 불가 타입으로는 배열을 만들 수 없다는 것이다. 이를 해결하는 방법 중 첫 번째는 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법이다. Object 배열을 생성한 다음 제네릭 배열로 형변환해보자. 이제 컴파일러는 오류 대신 경고를 내보낼 것이다. 이렇게도 할 수는 있지만 타입 안전하지 않다. 컴파일러는 타입 안전한지 증명할 방법이 없지만 개발자는 할 수 있다. 따라서 이 비검사 형변환이 프로그램의 타입 안전성을 해치지 않음을 스스로 확인해야 한다. 비검사 형변환이 안전함을 직접 증명했다면 범위를 최소로 좁혀 @SuppressWarnings 애너테이션으로 해당 경고를 숨기자. 그러면 깔끔히 컴파일되고, 명시적으로 형 변환하지 않아도 ClassCastException 걱정 없이 사용할 수 있게 된다. 두 번째 방법은 elements 필드의 타입을 E[]에서 Object[]로 바꾸는 것이다. 이러면 오류 대신 경고가 뜰 텐데 아까 이야기한 것처럼 직접 안전함을 증명하고 범위를 최소로 좁혀서 경고를 해결하자.

요약하자면,

배열과 제네릭에는 매우 다른 타입 규칙이 적용된다. 배열은 공변이고 실체화되는 반면, 제네릭은 불공변이고 타입 정보가 소거된다. 그 결과 배열은 런타임에는 타입 안전하지만 컴파일 타임에는 그렇지 않다. 제네릭은 반대다. 그래서 둘을 섞어 쓰기란 쉽지 않다. 둘을 섞어 쓰다가 컴파일 오류가 경고를 만나면, 가장 먼저 배열을 리스트로 대체하는 방법을 적용해보자.

반응형
반응형

제네릭을 사용해서 개발을 하다보면, 자주 마주하게 되는 비검사 경고에 대해서 이펙티브 자바에서는 어떻게 설명하고 있을까?

 

비검사 형변환 경고, 비검사 메서드 호출 경고, 비검사 매개변수화 가변인수 타입 경고, 비검사 변환 경고 등 제네릭을 사용하면 여러 경고를 맞이하게 된다. 제네릭에 익숙해질수록 마주치는 경고 수는 줄겠지만 새로 작성한 코드가 한번에 깨끗하게 컴파일되리라 기대하지는 말자.

대부분의 비검사 경고는 쉽게 제거할 수 있다. 컴파일러가 알려준 대로 수정하면 경고가 사라지긴 한다. 컴파일러가 알려준 타입 매개변수를 명시하지 않고, 자바 7부터 지원하는 다이아몬드 연사자만으로 해결할 수 있다. 그러면 컴파일러가 올바른 실제 타입 매개변수를 추론해준다. 제거하기 어려운 경고도 있지만, 곧바로 해결되지 않는 경고가 나타나도 포기하지말고 할 수 있는 한 모든 비검사 경고를 제거하자.

비검사 경고를 제거해야 하는 가장 큰 이유는 코드의 타입 안정성이 보장되기 때문이다. 그러면 런타임에 ClassCastException이 발생할 일이 없고, 의도한 대로 잘 동작하리라 확신할 수 있다. 경고를 제거할 수는 없지만 타입이 안전하다고 확신할 수 있다면 @SuppressWarnings("unchecked") 애너테이션을 달아 경고를 숨기자. 중요한 점은 타입이 안전하다고 확신할 수 있어야 한다는 점이다. 타입 안전을 검증하지 않은 채 경고를 숨기면 스스로에게 잘못된 보안 인식을 심어줄 수 있다. @SuppressWarning 애너테이션을 달고 있는 코드는 경고 없이 컴파일되겠지만, 런타임에는 여전히 ClassCastException이 발생할 수 있다. 안전하다고 검증된 비검사 경고를 그대로 두면 진짜 문제를 알리는 새로운 경고가 나와도 눈치채지 못할 수 있다. 제거하지 않은 수많은 거짓 경고 속에 새로운 경고가 파묻힐 것이기 때문이다. 

@SuppressWarning 애너테이션은 개별 지역변수 선언부터 클래스 전체까지 어떤 선언에도 달 수 있지만, 항상 가능한 좁은 범위에 적용해야 한다. 자칫 심각한 경고를 놓칠 수 있으니, 절대로 클래스 전체에 적용하지 말자. 

@SuppressWarning 애너테이션은 선언에만 달 수 있다. 깔끔하게 컴파일되고 비검사 경고를 숨기는 범위도 최소한으로 해당 애너테이션을 달도록 하자. @SuppressWarnings("unchecked") 애너테이션을 사용할 때면 그 경고를 무시해도 안전한 이유를 항상 주석으로 남겨야 한다. 다른 사람이 그 코드를 이해하는데 도움이 되며, 더 중요하게는, 다른 사람이 그 코드를 잘못 수정하여 타입 안전성을 잃는 상황을 줄여준다. 코드가 안전한 근거가 쉽게 떠오르지 않더라도 끝까지 포기하지 말자. 근거를 찾는 중에 그 코드가 사실은 안전하지 않다는 걸 발견할 수도 있다.

 

요약하자면,

비검사 경고는 중요하니 무시하지 말자. 모든 비검사 경고는 런타임에 ClassCastException을 일으킬 수 있는 잠재적 가능성을 뜻하니 최선을 다해 지거하자. 경고를 없앨 방법을 찾지 못하겠다면, 그 코드가 타입 안전성을 증명하고 가능한 한 범위를 좁혀 @SuppressWarnings("unchecked") 애너테이션으로 경고를 숨겨라. 그런 다음 경고를 숨기기로 한 근거를 주석으로 남겨라.

 

출처: 이펙티브 자바 3판

반응형
반응형

자바 8 전에는 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 방법이 없었다. 인터페이스에 메서드를 추가하면 보통은 컴파일 오류가 나는데, 추가된 메서드가 우연히 기존 구현체에 이미 존재할 가능성은 아주 낮기 때문이다. 자바 8에 와서 기존 인터페이스에 메서드를 추가할 수 있도록 디폴트 메서드를 소개했지만, 위험이 완전히 사라진 것은 아니다. 디폴트 메서드를 선언하면, 그 인터페이스를 구현한 후 디폴트 메서드를 재정의하지 않은 모든 클래스에서 디폴트 구현이 쓰이게 된다. 이처럼 자바에도 기존 인터페이스에 메서드를 추가하는 길이 열렸지만 모든 기존 구현체들과 매끄럽게 연동되리라는 보장은 없다. 자바 7까지의 세상에서는 모든 클래스가 "현재의 인터페이스에 새로운 메서드가 추가될 일은 영원히 없다"고 가정하고 작성됐으니 말이다. 디폴트 메서드는 구현 클래스에 대해 아무것도 모른 채 합의 없이 무작정 넣어질 뿐이다.

자바 8에서는 핵심 컬렉션 인터페이스들에 다수의 디폴트 메서드가 추가되었다. 주로 람다를 활용하기 위해서다. 자바 라이브러리의 디폴트 메서드는 코드 품질이 높고 범용적이라 대부분 상황에서 잘 동작한다. 하지만 생각할 수 있는 모든 상황에서 불변식을 해치지 않는 디폴트 메서드를 작성하기란 어려운 법이다. 자바 8의 collection 인터페이스에 추가된 removeIf 메서드를 예로 생각해 보자. 이 메서드는 주어진 불리언 함수(predicate)가 true를 반환하는 모든 원소를 제거한다. 디폴트 구현은 반복자를 이용해 순회하면서 각 원소를 인수로 넣어 predicate를 호출하고, predicate가 true를 반환하면 반복자의 remove 메서드를 호출해 그 원소를 제거한다. 

이러한 코드보다 더 범용적으로 구현하기도 어렵겠지만, 그렇다고 해서 현존하는 모든 Collection 구현체와 잘 어우러지는 것은 아니다. 대표적인 예가 org.apache.commons.collections4.collection.SynchronizedCollection이다. 아파치 커먼즈 라이브러리의 이 클래스는 java.util의 Collections.synchronizedCollection 정적 팩터리 메서드가 반환하는 클래스와 비슷하다. 아파치 버전은 클라이언트가 제공한 객체로 락을 거는 능력을 추가로 제공한다. 즉, 모든 메서드에서 주어진 락 객체로 동기화한 후 내부 컬렉션 객체에 기능을 위임하는 래퍼 클래스다.

아파치의 SynchronizedCollection 클래스는 지금도 활발히 관리되고 있지만, 이 책을 쓰는 시점엔 removeIf 메서드를 재정의하지 않고 있다. 이 클래스를 자바 8과 함께 사용한다면, 자신이 한 약속을 더 이상 지키지 못하게 된다. 다시 말해 모든 메서드 호출을 알아서 동기화해주지 못한다. removeIf의 구현은 동기화에 관해 아무것도 모르므로 락 객체를 사용할 수 없다. 따라서 SynchronizedCollection 인스턴스를 여러 스레드가 공유하는 환경에서 한 스레드가 removeIf를 호출하면 ConcurrentModificationException이 발생하거나 다른 예기치 못한 결과로 이어질 수 있다. 

자바 플랫폼 라이브러리에서도 이런 문제를 예방하기 위해 일련의 조치를 취했다. 예를 들어 구현한 인터페이스의 디폴트 메서드를 재정의하고, 다른 메서드에서는 디폴트 메서드를 호출하기 전에 필요한 작업을 수행하도록 했다. 예컨대 Collections.synchronizedCollection이 반환하는 package-private 클래스들은 removeIf를 재정의하고, 이를 호출하는 다른 메서드들은 디폴트 구현을 호출하기 전에 동기화를 하도록 했다. 하지만 자바 플랫폼에 속하지 않은 제 3의 기존 컬렉션 구현체들은 이런 언어 차원의 인터페이스 변화에 발맞춰 수정될 기회가 없었으며, 그중 일부는 여전히 수정되지 않고 있다. 디폴트 메서드는 기존 구현체에 런타임 오류를 일으킬 수 있다. 흔한 일은 아니지만, 나에게는 일어나지 않으리라는 보장도 없다. 자바 8은 컬렉션 인터페이스에 꽤 많은 디폴트 메서드를 추가했고, 그 결과 기존에 짜여진 많은 자바 코드가 영향을 받은 것으로 알려졌다. 

기존 인터페이스에 디폴트 메서드로 새 메서드를 추가하는 일은 꼭 필요한 경우가 아니면 피해야 한다. 추가하려는 디폴트 메서드가 기존 구현체들과 충돌하지는 않을지 심사숙고해야 함도 당연하다. 반면, 새로운 인터페이스를 만드는 경우라면 표준적인 메서드 구현을 제공하는데 아주 유용한 수단이며, 그 인터페이스를 더 쉽게 구현해 활용할 수 있게끔 해준다. 한편, 디폴트 메서드는 인터페이스로부터 메서드를 제거하거나 기존 메서드의 시그니처를 수정하는 용도가 아님을 명심해야 한다. 이런 형태로 인터페이스를 변경하면 반드시 기존 클라이언트를 망가뜨리게 된다. 핵심은 명백하다. 디폴트 메서드라는 도구가 생겼더라도 인터페이스를 설계할 때는 여전히 세심한 주의를 기울여야 한다.

새로운 인터페이스라면 릴리즈 전에 반드시 테스트를 거쳐야 한다. 수많은 개발자가 그 인터페이스를 나름의 방식으로 구현할 것이니, 서로 다른 방식으로 최소한 세 가지는 구현해봐야 한다. 또한 각 인터페이스의 인스턴스를 다양한 작업에 활용하는 클라이언트도 여러 개 만들어봐야 한다. 새 인터페이스가 의도한 용도에 잘 부합하는지를 확인하는 길은 이처럼 험난하다. 이런 작업들을 거치면 인터페이스를 릴리스하기 전에, 즉 바로잡을 기회가 아직 남았을 때 결함을 찾아낼 수 있다. 인터페이스를 릴리스한 후라도 결함을 수정하는 게 가능한 경우도 있겠지만, 절대 그 가능성에 기대서는 안 된다.

요약하자면, 

디폴트 인터페이스가 등장한 가장 큰 이유는 자바 8부터 지원하는 람다를 활용하기 위해서이다. 기존 인터페이스를 구현한 구현체들에게 새로운 메서드를 추가하기 위해서이고, 덕분에 람다의 다양한 함수들을 자바8 이전 버전에서 사용하던 클래스들에서도 사용할 수 있게 되었다. 하지만, 디폴트 메서드를 추가하는 일은 되도록 피하는 게 좋겠다. 디폴트 메서드는 관심사 분리와 설계적 측면에서의 기존 인터페이스의 관계와 맞지 않아 보인다. 따라서, 디폴트메서드를 세심한 주의 없이 추가하면 큰 사이드 이펙트를 맞이할 수 있다. 디폴트 메서드를 사용해야 한다면, 릴리즈 전에 반드시 꼼꼼히 테스트를 거치자.

 

출처: 이펙티브 자바 3판

반응형
반응형

추상클래스와 인터페이스는 자바가 다중 구현 매커니즘을 제공하는 방법이다. 자바 8부터는 인터페이스도 디폴트 메서드를 제공할 수 있게 되어 이제는 두 메커니즘 모두 인스턴스 메서드를 구현 형태로 제공할 수 있다. 둘의 가장 큰 차이는 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 한다는 점이다. 자바는 단일 상속만 지원하기 때문에, 추상 클래스 방식은 새로운 타입을 정의하는데 커다란 제약을 안게 되는 셈이다. 반면에 인터페이스가 선언한 메서드를 모두 정의하고 그 일반 규약을 잘 지킨 클래스라면 다른 어떤 클래스를 상속했든 같은 타입으로 취급된다.

자바에서는 기존 클래스 위에 새로운 추상 클래스를 넣기가 어렵지만, 인터페이스는 추가하기가 편하다. 인터페이스가 요구하는 메서드를 추가하고, 클래스 선언에 implements 구문만 추가하면 끝이다. 인터페이스의 장점은 믹스인 정의에 안성맞춤이라는 것이다. 믹스인이란 클래스가 구현할 수 있는 타입으로, 믹스인을 구현한 클래스에 원래의 주된 타입 외에도 특정 선택적 행위를 제공한다고 선언하는 효과를 준다. 예를 들어, Comparable은 자신을 구현한 클래스의 인스턴스들끼리는 순서를 정할 수 있다고 선언하는 믹스인 인터페이스다. 이처럼 대상 타입의 주된 기능에 선택적 기능을 혼합한다고해서 믹스인이라 부른다. 추상 클래스로는 믹스인을 정의할 수 없다. 이유는 기존 클래스에 덧씌울 수 없기 때문이다. 클래스는 한 개 이상의 부모 클래스를 가질 수 없고, 클래스 계층구조에는 믹스인을 삽입하기에 합리적인 위치가 없기 때문이다.

래퍼 클래스 관용구와 함께 사용하면 인터페이스는 기능을 향상시키는 안전하고 강력한 수단이 된다. 타입을 추상 클래스로 정의해두면 그 타입에 기능을 추가하는 방법은 상속뿐이다. 상속해서 만든 클래스는 래퍼 클래스보다 활용도가 떨어지고 깨지기는 더 쉽다. 인터페이스의 메서드 중 구현 방법이 명백한 것이 있다면, 그 구현을 디폴트 메서드로 제공해 프로그래머들의 일감을 덜어줄 수 있다. 디폴트 메서드를 제공할 때는 상속하려는 사람을 위한 설명을 @implSpec 자바독 태그를 붙여 문서화해야 한다. 디폴트 메서드에도 제약은 있다. 많은 인터페이스가 equals와 hashCode 같은 Object의 메서드를 정의하고 있지만, 이들은 디폴트 메서드로 제공해서는 안된다. 또한 인터페이스는 인스턴스 필드를 가질 수 없고, private 정적 메서드는 가질수 있지만, public이 아닌 정적 멤버는 가질 수 없다. 마지막으로, 자신이 만들지 않은 인터페이스에는 디폴트 메서드를 추가할 수 없다.

인터페이스와 추상 골격 구현 클래스를 함께 제공하는 식으로 인터페이스와 추상 클래스의 장점을 모두 취하는 방법도 있다. 인터페이스로는 타입을 정의하고, 필요하면 디폴트 메서드 몇 개도 함께 제공한다. 그리고 골격 구현 클래스는 나머지 메서드들까지 구현한다. 이렇게 해두면 단순히 골격 구현을 확장하는 것만으로 이 인터페이스를 구현하는데 필요한 일이 대부분 완료된다. 이것이 바로 템플릿 메서드 패턴이다.

관례상 인터페이스 이름이 Interface라면 그 골격 구현 클래스의 이름은 AbstractInterface로 짓는다. 좋은 예로, 컬렉션 프레임워크의 AbstractCollection, AbstractSet, AbstractList, AbstractMap 각각이 바로 핵심 컬렉션 인터페이스의 골격 구현이다.

골격 구현 클래스의 아름다움은 추상 클래스처럼 구현을 도와주는 동시에, 추상 클래스로 타입을 정의할 때 따라오는 심각한 제약에서는 자유롭다는 점에 있다. 골격 구현을 확장하는 것으로 인터페이스 구현이 거의 끝나지만, 꼭 이렇게 해야 하는 것은 아니다. 구조상 골격 구현을 확장하지 못하는 처지라면 인터페이스를 직접 구현해야 한다. 이런 경우라도 인터페이스가 직접 제공하는 디폴트 메서드의 이점을 여전히 누릴 수 있다. 또한, 골격 구현 클래스를 우회적으로 이용할 수도 있다. 인터페이스를 구현한 클래스에서 해당 골격 구현을 확장한 private 내부 클래스를 정의하고, 각 메서드 호출을 내부 클래스의 인스턴스에 전달하는 것이다. 래퍼 클래스와 비슷한 이 방식을 시뮬레이트한 다중 상속이라 하며, 다중 상속의 많은 장점을 제공하는 동시에 단점은 피하게 해준다.

 

골격 구현 작성은 상대적으로 쉽다. 가장 먼저, 인터페이스를 잘 살펴 다른 메서드들의 구현에 사용되는 기반 메서드들을 선정한다. 이 기반 메서드들은 골격 구현에서는 추상 메서드가 될 것이다. 그 다음으로, 기반 메서드들을 사용해 직접 구현할 수 있는 메서드를 모두 디폴트 메서드로 제공한다. 단 equals와 hashcode 같은 Object의 메서드는 디폴트 메서드로 제공하면 안 된다는 사실을 항상 유념하자. 만약 인터페이스의 메서드 모두가 기반 메서드와 디폴트 메서드가 된다면 골격 구현 클래스를 별도로 만들 이유는 없다. 기반 메서드나 디폴트 메서드로 만들지 못한 메서드가 남아 있다면, 이 인터페이스를 구현하는 골격 구현 클래스를 하나 만들어 남은 메서드들을 작성해 넣는다. 골격 구현 클래스에는 필요하면 public이 아닌 필드와 메서드를 추가해도 된다.

 

요약하자면,

일반적으로 다중 구현용 타입으로는 인터페이스가 가장 적합하다. 복잡한 인터페이스라면 구현하는 수고를 덜어주는 골격 구현을 함께 제공하는 방법을 꼭 고려해보자. 골격 구현은 '가능한 한' 인터페이스의 디폴트 메서드로 제공하여 그 인터페이스를 구현한 모든 곳에서 활용하도록 하는 것이 좋다. '가능한 한'이라고 한 이유는, 인터페이스에 걸려 있는 구현상의 제약 때문에 골격 구현을 추상 클래스로 제공하는 경우가 더 흔하기 때문이다.

 

출처: 이펙티브 자바 3판

반응형

+ Recent posts