Effective Java Review 02
Nov 02, 2019 조회수 157
Effective Java Review
1편이랑 이어지는 2편!
<br>
8. 계승이야기
- 메소드 호출과 달리, 계승은
캡슐화의 원칙
을 위반한다. - 하위 클래스의 정상작동이 상위클래스에 의존적이기 때문이다.
- 따라서, 상위클래스가 수정했을 때, 하위 클래스도 망가질 가능성이 높다.
- 사용하는 라이브러리의 클래스를 계승받아 커스텀 클래스를 구현한다면, 그 라이브러리의 버전이 바뀐다해도, 커스텀클래스가 온전히 동작한다고 확신할 수 있을까?
- 상위클래스의 메소드를 재정의 했을 때, 온전히 동작한다고 할 수 있을까?, 예를들어
HashSet.addAll()
은HashSet.add()
를 호출한다.HashSet
을 상속받아add()
를 재정의 했을 때, 상위클래스의HashSet.addAll()
은 온전히 동작할 수 있다 확신 할 수 있을까? - 계승받은 자식클래스에서 정의한 메소드가, 상위클래스에 추후에 정의 안된다고 할 수 있을까?
int a()
라는 메소드를 정의했을때, 상위클래스에서 정말운이없게String a()
를 정의한다면? 안전하다고 할 수 있을까? 계승
은 이런 많은 문제를 끌어 안고 있다, 문제가 될 가능성이 다분하다..- 따라서,
계승
은 상당히 많은 제약사항을 두고 있으며, 주의해야할 개념이다. - 이 문제를 해결할 수 있는 방법은
위임
이라는 개념을 사용하는 것이다! - 클래스를 계승받지 않고, 클래스를 사용하는 것이다.
- 상위클래스에 의존적인 하위클래스가 아닌,
참조를 하는 클래스
를 정의하는것이다. HashSet
을계승
받지 않고,HashSet
을참조
하는MyHashSet
을 정의한다,MyHashSet.add()
호출은, 참조하는hashSet.add()
를 호출하도록하자.- 하지만
위임
이 아닌계승
을 필요로 하는 상황은 주어질 것이다.. - 따라서
계승을 할 수 있는 메소드
를 정의 할 때는, 이 메소드가 어떻게 지금 현재 사용하고 있는지를 문서화해서 명확히 명시해야한다. 추후 어떠한 클라이언트 코드가계승
을 하려한다면, 충분히 고려 할 수 있도록 말이다! - 또한, 계승을 할 수 있는 필드, 메소드를 정의할때는 신중해야하며, 충분히 하위클래스를 구현해보고 테스트 해봐야한다.
계승 가능한 클래스
를 정의함에있어서 다음을 기억하자.- 생성자에서는 재정의 가능 메소드를 호출하면 안된다, 하위클래스에서 메소드를 재정의하면, 생성자에 문제가 생길 가능성이 높다.
- clone, readObject에도 재정의 가능한 메소드를 사용하면 안된다.
<br>
9. 인터페이스 이야기
- 추상클래스보다는 인터페이스를 사용하자
- 인터페이스는 추상클래스보다 많은 장점을 가지고 있다.
- 구현체는 여러 인터페이스를 혼합해 구현 할 수 있다.
- 또 인터페이스는 클래스와 달리
비 계층적
요소 이고,계층
이라는 개념을 고려하지 않고, 유연하게 사용 할 수 있다. - 추상클래스는 계승의 방법만을 제공하지만,
- 인터페이스는
포장클래스 숙어
를 제공한다. 포장클래스 숙어
는 계승이 아닌, 앞서말한위임
의 개념을 말한다.- 인터페이스의 공통적으로 사용하는 메소드를 정의하는 방법이 없는 것인데, 이는
추상 골격 구현 클래스
를 사용함으로써 추상클래스와 인터페이스의 장점을 활용 할 수 있다. 추상 골격 구현 클래스
는Abastract
를prefix
로 두어 보통 정의하는데, 다음과 같은 방법을 사용하자. ex)AbstractList
다른 메소드를 구현할 때 정의할 기본 메소드
를 추상메소드로 정의한다.- 다른 메소드들은
추상 골격 클래스
에서 구현하자. - 예를 들어,
Map
인터페이스를추상 골격 구현 클래스
로 구현한다면 - 다른메소드들이 사용하는
getKey()
,getValue()
와 같은 기본메소드는 추상메소드로, blabla()
와 같이기본메소드를 사용하는 메소드
는 구현한다.- 인터페이스를 사용함에 있어 주의해야할 점이 있다!
- 인터페이스는 한번 공개되고 널리 퍼지면, 수정이 거의 불가능함을 명심하자. 인터페이스의 메소드를 정의하고, 메소드 네임을 바꾼다면, 인터페이스의 구현체의 메소드는 모두 수정되어야 할 것이다.
- 인터페이스를 항시
자료형
으로 정의하는데 사용하자. - 인터페이스로 값을 참조하자. 추상화된 참조는 옳다.
상수 필드
가지는 인터페이스를 정의해서는 안된다!- 클래스가 어떤 상수를 가지는것은
구현
이며, 인터페이스의 역할이 아니다.
<br>
10. 클래스 이야기
- 클래스의 특정 필드값으로, 클래스를 태그를 달아주지 말자.
- 예를 들어,
Figure
클래스에 모양을 정의하는Shape
필드를태그
로서 정의해서, 이 태그값을 분기해서 사용하는 로직을 구현하지 말자. 태그
가 아닌 계층을 만들어Rectangle extends Figure
,Circle extends Figure
를 정의하자.태그 기반 클래스
는 난잡하면, 문제를 야기할 확률이 높다.- 람다식을 사용하자.
- 내부 클래스는 가능하면
static class
로 정의하자. - 내부 클래스를
non-static class
로 정의한다면, 항상 바깥 객체의 참조를 유지하게되고, 시간과 공간의 요구량이 늘어난다.
<br>
11.제네릭이야기
- 인자가 없는 제네릭을 쓰지말자. 형안전성을 잃게된다.
List, Set
쓰지마! - 다만, 클래스 리터럴에는 사용가능하다
List.class
instance of
에도 사용가능하다.if(a instance of Set) ~~
@SuppressWarning
어노테이션을 사용해, 경고를 제거하자@SuppressWarning
는 가장 최소한의 범위에 적용해야한다.- 제네릭은 배열대신
리스트
를 써야됨을 명심하자 String[]
에Long
을 넣는다면, 예외가 발생한다.- 다음 경우는 어떨까?
- 제네릭은 배열을 쓸 수 없지만, 하지만
배열
을 쓸수 있다고 한번, 가정해보자 List<String>[] stringLists = new List<>();
List<integer> intList = new List<>()
Object[] objects = stringLists
objects[0] = intList
List<String>[]
에List<Integer>
를 삽입 할 수 있다. 문제가 발생한다!!!!- 왜 그럼
제네릭
은배열
과 궁합이 맞지 않을까? - 우선 배열과 제네릭의 차이점을 이해해야 한다.
- 배열은
변하는 자료형
이다. 즉,Integer[]
는Objecrt[]
가 될 수 있다. - 반대로 제네릭은
불변 자료형
이다.List<Integer>
는List<Object>
가 될 수 없다. List<String>[]
의 상위클래스인Object[]
에, 실행시점에는List<Integer>
,List<String>
도 삽입될 수 있는 것이다!- 또 배열은
실체화
자료형이다. 말이 어렵지만, 실행시점에도Integer[]
라는 자료형을 그대로 가지고 있다는 것이다. - 반대로, 제네릭은
삭제
과정을 가진다, 말이 좀 어렵지만List<Integer>
는 실행 시점에List<E>
로 변경되고, 자료형의 정보가 삭제된다는 것이다. (삭제
라는 과정이 있기때문에,제네릭
이 유연함을 가질 수 있는 것이다.) - 번역하면, 리스트가 아니더라도 제네릭 타입에
Integer
를 주입하더라도, 실행시점엔E
로 변경된다. - 다시 한번 번역하면, 제네릭 타입 배열에
Integer[]
를 주입하면, 실행시점에는E[]
로 변경된다, 아니 안된다! - 앞서 말했듯이, 배열은
실체화
자료형이기 때문에, 이는 허용되지 않는다. - 따라서 제네릭은
배열
을 쓰면 안된다. 궁합이 안맞는다.리스트
를 사용해야한다, 애초에 컴파일도 안된다.. - 하지만 이 지침은 항상 옳은것은 아니다.
- 제네릭
Stack<E>
클래스를 보면 내부적으로(Object[]) 배열
을 사용하고 있다. - 아니 왜 제네릭으로 받아서,
Object[]
로 치환해서 사용하지? - 왜냐하면
리스트
는 자바의 기본자료형이 아니기 때문이다. - 예를들어
ArrayList
같은 제네릭 타입은 배열을 이용해 구현해야 한다. - 예를들어,
HashMap
은 성능 이슈 때문에 배열을 이용해 구현해야 한다. - 가능하면 제네릭 클래스, 제네릭 메소드를 사용해 유연함을 늘리는 것이 좋다.
한정적 와일드 카드
를 써서 유연성을 높이자.- 다음과 같이 말이다.
List<? extend AClass>
한정적 와일드 카드
에는 규칙이 있다.- 생산자에는, 즉 어떤 값을 추가하는 예를 들어
push()
라면,<? extends E>
를 사용하자. - ex)
push(List<? extends E>)
- // 이해가 좀 어려운데, 삽입하는 것이니, 하위클래스도 넣을수 있는 것이 좋다!
- 소비자에는, 즉 어떤 값을 사용하는, 예를들어
pop()
이라면<? super E>
를 사용하자. - ex)
popAll(List<? super E>)
- // 이해가 좀 어려운데, 꺼내는 것이니 상위캐스팅해서 꺼내는 것도 좋다!
- 하지만, 리턴값은 와일드 카드 자료형을 사용하면안된다,
E
를 사용해야한다.
'Effective Java Review 02' 관련된 다른글
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.