[핵심 정리]
상속은 강력하지만 캡슐화를 해친다는 문제가 있다. 상속을 사용해야 할때는 상위 클래스와 하위 클래스의 관계가 순수하게 is - a일 때만 써야 한다. 그러나 is - a 관계라고 해도 안심하고 쓸수 만은 없는게 상위 클래스가 확장을 고려해서 설계되지 않았거나 하위 클래스의 패키지가 상위 클래스와 다를 땐 여전히 문제가 될 수 있다.
상속의 취약점을 피하고 캡슐화를 지키기 위해선 우리는 컴포지션과 전달을 사용해야 한다. 특히 래퍼 클래스로 구현할 적당한 인터페이스가 있다면 더욱 상속 대신 컴포지션과 전달을 사용해야 한다.래퍼 클래스는 하위 클래스보다 견고하고 강력하기 때문이다.
extends
를 사용해 상속 받을 수 있는 개념이다.private
접근 제한자로 설정해둔 멤버의 경우엔 하위 클래스도 접근이 불가합니다.[부모 클래스]
package test;
import org.junit.jupiter.api.Test;
public class Parent {
private final String name;
String phoneNumber;
protected String email;
public int age;
public Parent(String name) {
this.name = name;
}
void methodA(){
System.out.println("parent method");
}
}
[자식 클래스]
package test;
public class Child extends Parent{
public Child(String name) {
super(name);
}
}
extends
를 사용해 부모 클래스를 상속 받아봅시다.[접근 가능한 범위]
package test;
import org.junit.jupiter.api.Test;
public class Main {
@Test
void test(){
Child child = new Child("ddd");
child.methodA();
child.age = 10;
child.email = "dd";
child.phoneNumber= "191919191";
}
}
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
public InstrumentedHashSet(int initCap, float loadFactor){
super(initCap, loadFactor);
}
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c);
}
public int getAddCount() {
return addCount;
}
}
// addAll()
/**
* {@inheritDoc}
*
* @implSpec
* This implementation iterates over the specified collection, and adds
* each object returned by the iterator to this collection, in turn.
*
* <p>Note that this implementation will throw an
* {@code UnsupportedOperationException} unless {@code add} is
* overridden (assuming the specified collection is non-empty).
*
* @throws UnsupportedOperationException {@inheritDoc}
* @throws ClassCastException {@inheritDoc}
* @throws NullPointerException {@inheritDoc}
* @throws IllegalArgumentException {@inheritDoc}
* @throws IllegalStateException {@inheritDoc}
*
* @see #add(Object)
*/
public boolean addAll(Collection<? extends E> c) {
boolean modified = false;
for (E e : c)
if (add(e))
modified = true;
return modified;
}
다음 릴리즈에서 상위 클래스에 새로운 메서드가 추가된다고 가정해보자, 이때 보안 때문에 컬렉션에 추가된 모든 원소가 특정 조건을 만족해야 하는 프로그램을 생각해보자
기존에 존재하던 Hashtable, Vector를 컬렉션 프레임워크에 포함시키자 이와 관련된 보안 구멍을 수정해야 하는 일이 발생했다.
private
필드로 기존 클래스의 인스턴스를 참조하게 하자import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
public class InstrumentedSetWrapper<E> implements Set<E> {
private Set<E> set;
private int addCount = 0;
public InstrumentedSetWrapper(Set<E> set) {
this.set = set;
}
public InstrumentedSetWrapper(int initCap, float loadFactor) {
this.set = new HashSet<>(initCap, loadFactor);
}
@Override
public boolean add(E e) {
addCount++;
return set.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return set.addAll(c);
}
@Override
public int size() {
return set.size();
}
// 나머지 Set 인터페이스의 메서드들도 구현해야 합니다.
public int getAddCount() {
return addCount;
}
}
extends
를 사용해 상속해 구현하고 있다.