09 컬렉션
컬렉션의 개념은 여러 가지 메타포를 혼합한 것이다.
실제 컬렉션은 어떻게 사용하느냐에 따라 중심 메타포가 달라진다.
컬렉션 인터페이스는 여러 객체의 묶음에 대해 제각각 다른 의미를 전달하며,
컬렉션 구현은 주로 선응에 관련된 프로그래머의 의도를 전달한다.
따라서 컬렉션 사용과 커뮤니케이션을 위한 코드 작성 사이에는 밀접한 관련이 있다.
메타포
첫째 메타포는 여러 값을 가진 변수multi-valued variable이다.
컬렉션 변수는 여러 객체를 한번에 가리키는 역할을 하므로 별도의 객체라를 사실은 중요하지 않다.
중요한 건 컬렉션이 가리키는 객체가 어떤 것이냐이다.
컬렉션에 사용되는 다른 메타포는 컬렉션이 객체라는 것이다.
값을 얻는 것도 가능하고, 인자로 전달하는 것도 가능하며, 동등성equality 테스트를 할 수도 있고, 메시지를 보낼 수도 있다.
컬렉션은 여러 관련 있는 인터페이스와 구현의 집합이기 때문에, 확장된 인터페이스와 새로운 구현을 통해 확장이 가능하다.
컬렉션은 여러 값을 가진 변수이기도 하지만 객체이기도 하다.
두 메타포를 섞어놓으면 묘한 상황이 발생한다.
컬렉션은 인자로 전달 가능한 객체로 구현되므로, 변수 값이 아닌 변수 자체를 인자로 전달하는 참조 호출의 효과를 얻을 수 있다.
코드를 읽기 어려운 것과 컬렉션이 수정되는 곳을 예측하기 어려운 상황을 피하기 위해 컬렉션 사용에는 몇 가지 규범이 있다.
컬렉션에 대한 다른 메타포는 수학적 집합이다.
수학에서 집합이 여러 원소의 모임인 것과 마찬가지로 컬렉션은 객체의 모임이다.
수학에서 집합에 대한 기본적 연산은 집합의 크기를 구하는 것(size() 메소드)과 집합에 속해있는지 알아보는(contains() 메소드) 테스트이다.
집합의 다른 기본 연산인 합집합, 교집합, 차집합, 여집합 등은 컬렉션에 적용되지 않는다.
연산이 유용하지 않아서일 수도 있고, 컬렉션에서 제공하지 않으므로 사용하지 않는 것인지는 명확하지 않다.
이슈
원칙적으로 컬렉션을 사용하려면 가장 일반적인 인터페이스를 사용해서 선언하고, 가장 구체적인 구현 클래스를 사용하는 것이 좋다.
하지만 이 원칙이 절대적이진 않다.
여러 인터페이스를 사용하면 통일성이 깨져서 코드 품질이 크게 떨어진다.
예로 Iterable, Collection, List 등의 다양한 인터페이를 선언하는 건 코드를 읽는데 별 다른 이득이 없으므로 한 가지 인터페이스로 통일하는 게 깔끔하다.
컬렉션이 표현하는 첫째 개념은 크기이다.
컬렉션이 표현하는 둘째 개념은 원소 간의 순서에 의미가 있는지 여부이다.
컬렉션이 나타내는 다른 개념은 원소의 독자성uniqueness이다. 컬렉션에 속해 있는지 여부가 중요할 수도 있지만 어떤 원소가 몇 번 나타나는지에 대한 정보가 중요한 경우도 있다.
원소에는 순차 열람이 기본이지만, 어떤 경우는 키를 통해 원소를 저장하고 얻는 게 중요할 수도 있다.
이는 성능에 관한 프로그래머의 의도룰 전달해주는 것이다.
순차 열람은 선형탐색에 문제가 없으므로 Collection을 사용하며, 키를 통해 원소에 접근한다면 Set, Map을 사용한다.
이런 의도를 드러내면 시간과 공간면에서 프로그램을 최적화할 수 있다.
성능
성능과 컬렉션 사이에 밀접한 관계가 있는 이유는 컬렉션의 크기에 제한이 없기 때문이다.
성능에 중점을 둬서 컬렉션을 사용한다면 우선 단순한 구현으로 시작해서 필요에 따라 좀더 특화된 컬렉션을 사용한다.
성능과 관련된 결정은 설계에 어느 정도 변화가 필요해도 그 변화에 따른 영향 범위를 한정지어야 한다.
원하는 성능이 충분하다면 그 이상의 튜닝은 필요가 없다.
인터페이스
인터페이스를 보고 코드를 읽는 사람들은 여러 질문에 답을 찾을 수 있게 된다.
이는 컬렉션이 특정 순서대로 정렬되어 있는지, 원소 간의 중복을 허용하는지, 키를 통한 원소 검색이 가능한지를 알려주기 때문이다.
- Array: 가장 단순하지만 가장 유연하지 못하다. 크기가 고정되어 있지만 원소 접근 방법이 쉽고 빠르다.
- Iterable: 기본적인 컬렉션 인터페이스로 순차 열람을 지원한다.
- Collection: 원소 추가/제거/포함 테스트를 지원한다.
- List: 순서가 있으며 그 위치를 통해 원소에 접근할 수 있다.
- SortedSet: 중복 원소가 없으며 원소 간의 순서가 정해져 있다.
- Map: 키에 의해 원소를 저장하고 접근한다.
Array
배열은 다른 컬렉션과 같은 프로토콜을 갖고 있지 않으므로 배열을 컬렉션으로 변환하는 일은 상대적으로 복잡하다.
다른 컬렉션과 달리 배열의 크기는 생성시 고정이다.
단순 연산의 경우 배열은 다른 컬렉션에 비해 시간, 공간 모든 면에서 효율적이다.
배열 접근 elements[i]는 ArrayList의 elements.get(i) 보다 10배 이상 빠르다.
그외 유연성의 문제 때문에 컬렉션을 사용하는 것이 낫지만, 성능이 중요한 경우라면 배열을 사용하는 것도 좋다.
Iterable
Iterable은 그 변수가 여러 개의 값을 가지고 있다는 걸 의미한다.
Iterable의 문제는 원소 추가는 막을 수 있지만 제거는 막지 못한다는 점이다.
이를 위해 수정 불가능한 컬렉션으로 래핑할 수도 있고, 수정하려고 할 때 예외를 던질 수도 있으며, 복사본을 만드는 방법도 있다.
Iterable 자체는 순차 열람 뿐이어서 이것을 상속받은 하위 인터페이스들을 살펴보는 게 좋다.
Collection
Collection은 Iterable을 상속해서 원소 추가, 삭제, 검색, 크기 측정 등의 메소드를 추가로 지원한다.
컬렉션은 수학에서의 집합과 유사하다.
하지만 합집합(addAll), 교집합(retainAll), 차집합(removeAll)의 결과는 컬렉션 자체를 바꾸는 것이 아니라 새로 할당된 컬렉션을 반환한다.
List
List는 Collection을 기반으로 해서 원소 간에 정해진 순서를 부여한 것이다.
따라서 List를 사용하면 인덱스를 통해 어떤 원소를 접근할 수 있다.
Set
Set은 중복 원소를 허용하지 않는 컬렉션이다.
수학에서 사용하는 집합의 개념과 비슷하지만 collection과 다르게 Set에 원소를 추가하면 변경된 컬렉션을 반환하는 것이 아니라 컬렉션 자체가 바뀐다.
Set은 원소가 속해 짔는지 여부가 중요하고, 출현 횟수가 중요하지 않은 경우에 쓰면 좋다.
또 원소 사이에 순서가 없으므로 원소 사이에 상호작용이 없는 경우에도 문제되지 않는다.
SortedSet
순서는 유지되지만 중복 원소가 없는 컬렉션이 필요한 경우에 SortedSet을 사용한다.
SortedSet은 Comparator에 의해 순서를 정한다.
명시적인 순서를 정하지 않으면 알파벳 순서가 사용된다.
Map
Map은 List 처럼 키를 사용해서 원소를 저장하고 임의의 객체를 키로 사용할 수 있다.
Map은 다른 컬렉션 인터페이스의 특징을 사용하지 않으므로 다른 컬렉션 인터페이스를 상속하지 않는다.
Map은 내부적으로 키에 대한 컬렉션과 데이터에 대한 컬렉션의 2개 컬렉션을 유지한다.
구현
컬렉션에 대해 구현 클래스를 선택하는 것은 주로 성능과 관련이 있다.
일단 단순한 구현으로 시작하고 경험에 따라 튜닝하는 것이 좋다.
Figure 9.1 Collection interfaces and classes
컬렉션 중 가장 많이 사용하는 클래스는 ArrayList이며 그 다음은 HashSet이다.
어떤 클래스를 사용할지 잘 모르겠으면 이 둘 중에서 적합한 것 하나를 고르는 것도 좋다.
Collection
Collection 인터페이스에 대한 기본 구현 클래스는 ArrayList이다.
ArrayList를 사용할 때 성능상 문제가 될만한 부분은 크기에 따라 연산 시간이 커지는 contatins(), remove() 등이 있다.
성능 제약이 있다면 HashSet으로 바꾸는 걸 고려해 볼 수 있다.
ArrayList에 중복 원소가 없는 경우라면 HashSet으로 바꿔도 별 차이가 없을 수도 있다.
List
두 가지 구현은 ArrayList와 LinkedList 이다.
ArrayList는 원소 접근이 빠르고 원소 추가 및 제거가 느린데 LinkedList는 반대이다.
add(), remove()를 많이 사용한다면 ArrayList에서 LinkedList로 바꾸는 걸 고려하라.
Set
구현에는 HashSet, LinkedHashSet, TreeSet(SortedSet의 구현) 세 가지가 있다.
HashSet은 빠르지만 원소 간의 순서를 보장해주지 않는다.
LinkedHashSet은 원소 간 순서를 삽입된 순서로 보장해주지만 원소 추가 삭제 시 30% 정도 시간이 더 걸린다.
TreeSet은 Comparator에 따라 원소를 정렬하지만 원소 추가/삭제 시간이 log n에 비례해서 커진다.
원소가 순서를 보장하고 싶다면 LinkedHashSet을 사용하라.
Figure 9.2 Comparing ArrayList and HashSet as implementations of Collection
Map
HashMap은 가장 빠르고 단순하다.
LinkedHashMap은 컬렉션에 추가된 원소 간 순서를 보장한다.
TreeMap은 키의 순서에 따라 순차 열람이 가능하지만 원소 추가 제거 시간이 log n에 비례한다.
Figure 9.3 Comparing ArrayList and LinkedList
Collections
Collections는 컬렉션 인터페이스에 넣기 적절하지 않은 기능들을 모아 놓았다.
Figure 9.4 Comparing Set implementations
검색
indexOf() 연산에 걸리는 시간은 리스트의 크기에 비례한다.
원소들이 정렬되어 있다면 Collections.binarySearch()를 사용해 효율적으로 원소에 대한 인덱스를 구할 수 있다.
만약 정렬되어 있지 않으면 결과는 예측할 수 없다.
Figure 9.5 Comparing Map implementations
정렬
reverse()는 원소 간의 순서를 거꾸로 바꾼다.
shuffle()는 순서를 임의로 바꾼다.
sort()는 오름차순으로 원소를 정렬한다.
ArrayList와 LinkedList의 정렬 성능은 같은데
그 이유는 컬렉션의 원소들이 일단 배열로 복사되어 정렬된 후 다시 본래 컬렉션으로 복사되기 때문이다.
Figure 9.6 Comparing indexOf() and binary search
수정 불가능한 컬렉션
수정 불가능한 컬렉션을 전달하려면 Collections로 래핑한 후에 예외를 발생시키도록 할 수 있다.
이 기법은 Collection, List, Set, Map에도 적용할 수 있다.
단일 원소 컬렉션
하나의 원소를 전달하는데 컬렉션을 사용해야 하는 경우는 Set을 반환하는 Collections.singeton()을 사용할 수 있다.
이때 반환되는 컬렉션은 수정할 수 없다.
무원소 컬렉션
컬렉션 인터페이스를 사용하지만 전달할 원소가 없다면 Collections.emptyList()를 사용해 수정할 수 없는 무원소 컬렉션을 생성할 수 있다.
컬렉션 확장
예로 다음과 같은 코드이다.
class Library extends ArrayList { ... }
이 방식은 몇 가지 문제가 있다.
우선 컬렉션에서 제공하는 많은 연산들이 클라이언트 측에 부적절할 수 있다.
clear(), toArray()와 같은 연산은 수행하면 안되는데 문제가 없는 경우라고 해도 여러 메타포가 섞여서 코드의 복잡도가 높아진다.
최악으로 필요가 없는 연산을 모두 새로 재정의해서 예외를 발생시키는 경우가 있는데 일부 기능을 위해 쓸데 없는 기능에 훨씬 많은 예외 코드를 작성할 필요는 없다.
또 다른 문제점으로 상속을 망쳐놓을 수 있다.
몇 줄 안되는 구현을 재사용하기 위해 다른 상속을 할 수 없을 수도 있다.
이런 경우에는 상속 보다는 위임을 사용하는 것이 좋다.
09 컬렉션
컬렉션의 개념은 여러 가지 메타포를 혼합한 것이다.
실제 컬렉션은 어떻게 사용하느냐에 따라 중심 메타포가 달라진다.
컬렉션 인터페이스는 여러 객체의 묶음에 대해 제각각 다른 의미를 전달하며,
컬렉션 구현은 주로 선응에 관련된 프로그래머의 의도를 전달한다.
따라서 컬렉션 사용과 커뮤니케이션을 위한 코드 작성 사이에는 밀접한 관련이 있다.
메타포
첫째 메타포는 여러 값을 가진 변수multi-valued variable이다.
컬렉션 변수는 여러 객체를 한번에 가리키는 역할을 하므로 별도의 객체라를 사실은 중요하지 않다.
중요한 건 컬렉션이 가리키는 객체가 어떤 것이냐이다.
컬렉션에 사용되는 다른 메타포는 컬렉션이 객체라는 것이다.
값을 얻는 것도 가능하고, 인자로 전달하는 것도 가능하며, 동등성equality 테스트를 할 수도 있고, 메시지를 보낼 수도 있다.
컬렉션은 여러 관련 있는 인터페이스와 구현의 집합이기 때문에, 확장된 인터페이스와 새로운 구현을 통해 확장이 가능하다.
컬렉션은 여러 값을 가진 변수이기도 하지만 객체이기도 하다.
두 메타포를 섞어놓으면 묘한 상황이 발생한다.
컬렉션은 인자로 전달 가능한 객체로 구현되므로, 변수 값이 아닌 변수 자체를 인자로 전달하는 참조 호출의 효과를 얻을 수 있다.
코드를 읽기 어려운 것과 컬렉션이 수정되는 곳을 예측하기 어려운 상황을 피하기 위해 컬렉션 사용에는 몇 가지 규범이 있다.
컬렉션에 대한 다른 메타포는 수학적 집합이다.
수학에서 집합이 여러 원소의 모임인 것과 마찬가지로 컬렉션은 객체의 모임이다.
수학에서 집합에 대한 기본적 연산은 집합의 크기를 구하는 것(size() 메소드)과 집합에 속해있는지 알아보는(contains() 메소드) 테스트이다.
집합의 다른 기본 연산인 합집합, 교집합, 차집합, 여집합 등은 컬렉션에 적용되지 않는다.
연산이 유용하지 않아서일 수도 있고, 컬렉션에서 제공하지 않으므로 사용하지 않는 것인지는 명확하지 않다.
이슈
원칙적으로 컬렉션을 사용하려면 가장 일반적인 인터페이스를 사용해서 선언하고, 가장 구체적인 구현 클래스를 사용하는 것이 좋다.
하지만 이 원칙이 절대적이진 않다.
여러 인터페이스를 사용하면 통일성이 깨져서 코드 품질이 크게 떨어진다.
예로 Iterable, Collection, List 등의 다양한 인터페이를 선언하는 건 코드를 읽는데 별 다른 이득이 없으므로 한 가지 인터페이스로 통일하는 게 깔끔하다.
컬렉션이 표현하는 첫째 개념은 크기이다.
컬렉션이 표현하는 둘째 개념은 원소 간의 순서에 의미가 있는지 여부이다.
컬렉션이 나타내는 다른 개념은 원소의 독자성uniqueness이다. 컬렉션에 속해 있는지 여부가 중요할 수도 있지만 어떤 원소가 몇 번 나타나는지에 대한 정보가 중요한 경우도 있다.
원소에는 순차 열람이 기본이지만, 어떤 경우는 키를 통해 원소를 저장하고 얻는 게 중요할 수도 있다.
이는 성능에 관한 프로그래머의 의도룰 전달해주는 것이다.
순차 열람은 선형탐색에 문제가 없으므로 Collection을 사용하며, 키를 통해 원소에 접근한다면 Set, Map을 사용한다.
이런 의도를 드러내면 시간과 공간면에서 프로그램을 최적화할 수 있다.
성능
성능과 컬렉션 사이에 밀접한 관계가 있는 이유는 컬렉션의 크기에 제한이 없기 때문이다.
성능에 중점을 둬서 컬렉션을 사용한다면 우선 단순한 구현으로 시작해서 필요에 따라 좀더 특화된 컬렉션을 사용한다.
성능과 관련된 결정은 설계에 어느 정도 변화가 필요해도 그 변화에 따른 영향 범위를 한정지어야 한다.
원하는 성능이 충분하다면 그 이상의 튜닝은 필요가 없다.
인터페이스
인터페이스를 보고 코드를 읽는 사람들은 여러 질문에 답을 찾을 수 있게 된다.
이는 컬렉션이 특정 순서대로 정렬되어 있는지, 원소 간의 중복을 허용하는지, 키를 통한 원소 검색이 가능한지를 알려주기 때문이다.
Array
배열은 다른 컬렉션과 같은 프로토콜을 갖고 있지 않으므로 배열을 컬렉션으로 변환하는 일은 상대적으로 복잡하다.
다른 컬렉션과 달리 배열의 크기는 생성시 고정이다.
단순 연산의 경우 배열은 다른 컬렉션에 비해 시간, 공간 모든 면에서 효율적이다.
배열 접근 elements[i]는 ArrayList의 elements.get(i) 보다 10배 이상 빠르다.
그외 유연성의 문제 때문에 컬렉션을 사용하는 것이 낫지만, 성능이 중요한 경우라면 배열을 사용하는 것도 좋다.
Iterable
Iterable은 그 변수가 여러 개의 값을 가지고 있다는 걸 의미한다.
Iterable의 문제는 원소 추가는 막을 수 있지만 제거는 막지 못한다는 점이다.
이를 위해 수정 불가능한 컬렉션으로 래핑할 수도 있고, 수정하려고 할 때 예외를 던질 수도 있으며, 복사본을 만드는 방법도 있다.
Iterable 자체는 순차 열람 뿐이어서 이것을 상속받은 하위 인터페이스들을 살펴보는 게 좋다.
Collection
Collection은 Iterable을 상속해서 원소 추가, 삭제, 검색, 크기 측정 등의 메소드를 추가로 지원한다.
컬렉션은 수학에서의 집합과 유사하다.
하지만 합집합(addAll), 교집합(retainAll), 차집합(removeAll)의 결과는 컬렉션 자체를 바꾸는 것이 아니라 새로 할당된 컬렉션을 반환한다.
List
List는 Collection을 기반으로 해서 원소 간에 정해진 순서를 부여한 것이다.
따라서 List를 사용하면 인덱스를 통해 어떤 원소를 접근할 수 있다.
Set
Set은 중복 원소를 허용하지 않는 컬렉션이다.
수학에서 사용하는 집합의 개념과 비슷하지만 collection과 다르게 Set에 원소를 추가하면 변경된 컬렉션을 반환하는 것이 아니라 컬렉션 자체가 바뀐다.
Set은 원소가 속해 짔는지 여부가 중요하고, 출현 횟수가 중요하지 않은 경우에 쓰면 좋다.
또 원소 사이에 순서가 없으므로 원소 사이에 상호작용이 없는 경우에도 문제되지 않는다.
SortedSet
순서는 유지되지만 중복 원소가 없는 컬렉션이 필요한 경우에 SortedSet을 사용한다.
SortedSet은 Comparator에 의해 순서를 정한다.
명시적인 순서를 정하지 않으면 알파벳 순서가 사용된다.
Map
Map은 List 처럼 키를 사용해서 원소를 저장하고 임의의 객체를 키로 사용할 수 있다.
Map은 다른 컬렉션 인터페이스의 특징을 사용하지 않으므로 다른 컬렉션 인터페이스를 상속하지 않는다.
Map은 내부적으로 키에 대한 컬렉션과 데이터에 대한 컬렉션의 2개 컬렉션을 유지한다.
구현
컬렉션에 대해 구현 클래스를 선택하는 것은 주로 성능과 관련이 있다.
일단 단순한 구현으로 시작하고 경험에 따라 튜닝하는 것이 좋다.
Figure 9.1 Collection interfaces and classes
컬렉션 중 가장 많이 사용하는 클래스는 ArrayList이며 그 다음은 HashSet이다.
어떤 클래스를 사용할지 잘 모르겠으면 이 둘 중에서 적합한 것 하나를 고르는 것도 좋다.
Collection
Collection 인터페이스에 대한 기본 구현 클래스는 ArrayList이다.
ArrayList를 사용할 때 성능상 문제가 될만한 부분은 크기에 따라 연산 시간이 커지는 contatins(), remove() 등이 있다.
성능 제약이 있다면 HashSet으로 바꾸는 걸 고려해 볼 수 있다.
ArrayList에 중복 원소가 없는 경우라면 HashSet으로 바꿔도 별 차이가 없을 수도 있다.
List
두 가지 구현은 ArrayList와 LinkedList 이다.
ArrayList는 원소 접근이 빠르고 원소 추가 및 제거가 느린데 LinkedList는 반대이다.
add(), remove()를 많이 사용한다면 ArrayList에서 LinkedList로 바꾸는 걸 고려하라.
Set
구현에는 HashSet, LinkedHashSet, TreeSet(SortedSet의 구현) 세 가지가 있다.
HashSet은 빠르지만 원소 간의 순서를 보장해주지 않는다.
LinkedHashSet은 원소 간 순서를 삽입된 순서로 보장해주지만 원소 추가 삭제 시 30% 정도 시간이 더 걸린다.
TreeSet은 Comparator에 따라 원소를 정렬하지만 원소 추가/삭제 시간이 log n에 비례해서 커진다.
원소가 순서를 보장하고 싶다면 LinkedHashSet을 사용하라.
Figure 9.2 Comparing ArrayList and HashSet as implementations of Collection
Map
HashMap은 가장 빠르고 단순하다.
LinkedHashMap은 컬렉션에 추가된 원소 간 순서를 보장한다.
TreeMap은 키의 순서에 따라 순차 열람이 가능하지만 원소 추가 제거 시간이 log n에 비례한다.
Figure 9.3 Comparing ArrayList and LinkedList
Collections
Collections는 컬렉션 인터페이스에 넣기 적절하지 않은 기능들을 모아 놓았다.
Figure 9.4 Comparing Set implementations
검색
indexOf() 연산에 걸리는 시간은 리스트의 크기에 비례한다.
원소들이 정렬되어 있다면 Collections.binarySearch()를 사용해 효율적으로 원소에 대한 인덱스를 구할 수 있다.
만약 정렬되어 있지 않으면 결과는 예측할 수 없다.
Figure 9.5 Comparing Map implementations
정렬
reverse()는 원소 간의 순서를 거꾸로 바꾼다.
shuffle()는 순서를 임의로 바꾼다.
sort()는 오름차순으로 원소를 정렬한다.
ArrayList와 LinkedList의 정렬 성능은 같은데
그 이유는 컬렉션의 원소들이 일단 배열로 복사되어 정렬된 후 다시 본래 컬렉션으로 복사되기 때문이다.
Figure 9.6 Comparing indexOf() and binary search
수정 불가능한 컬렉션
수정 불가능한 컬렉션을 전달하려면 Collections로 래핑한 후에 예외를 발생시키도록 할 수 있다.
이 기법은 Collection, List, Set, Map에도 적용할 수 있다.
단일 원소 컬렉션
하나의 원소를 전달하는데 컬렉션을 사용해야 하는 경우는 Set을 반환하는 Collections.singeton()을 사용할 수 있다.
이때 반환되는 컬렉션은 수정할 수 없다.
무원소 컬렉션
컬렉션 인터페이스를 사용하지만 전달할 원소가 없다면 Collections.emptyList()를 사용해 수정할 수 없는 무원소 컬렉션을 생성할 수 있다.
컬렉션 확장
예로 다음과 같은 코드이다.
이 방식은 몇 가지 문제가 있다.
우선 컬렉션에서 제공하는 많은 연산들이 클라이언트 측에 부적절할 수 있다.
clear(), toArray()와 같은 연산은 수행하면 안되는데 문제가 없는 경우라고 해도 여러 메타포가 섞여서 코드의 복잡도가 높아진다.
최악으로 필요가 없는 연산을 모두 새로 재정의해서 예외를 발생시키는 경우가 있는데 일부 기능을 위해 쓸데 없는 기능에 훨씬 많은 예외 코드를 작성할 필요는 없다.
또 다른 문제점으로 상속을 망쳐놓을 수 있다.
몇 줄 안되는 구현을 재사용하기 위해 다른 상속을 할 수 없을 수도 있다.
이런 경우에는 상속 보다는 위임을 사용하는 것이 좋다.