Effective Java Review 02
Nov 02, 2019 조회수 172
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 = stringListsobjects[0] = intListList<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' 관련된 다른글
이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.