스레드를 공부하고 얘기해보는 시간을 갖다가, Thread는 그 클래스를 상속시켜서 구현한다는 얘기가 나오면서 상속보다는 조합을 사용하라는 말이 나왔는데, 여기서 궁금증이 생겨서 포스팅을 시작했다. 사실 조합에 대한 개념이 아예 없기도 해서,,
(내가 아는 조합은 combination 조합밖에 없는girl)
상속
은 부모 클래스의 기능을 자식 클래스가 그대로 물려받는 것
을 일컫는다. 실제 코드에서는 extends
키워드를 활용해 상속을 표시한다.
//상속의 기본적인 예시
class Student {
String name;
void setName(String name) {
this.name = name;
}
}
class St1 extends Student {
void greet() {
System.out.println("안녕하세요! 저는 "+this.name+"입니다!");
}
//보통은 이렇게 부모의 기능에다가 추가적인 기능을 더 넣어준다.
}
public static void main(String[] args) {
St1 st1 = new St1();
st1.setName("주디");
st1.greet();
//안녕하세요! 저는 주디입니다!
}
Student ex1 = new St1();
//상속 관계에 있기 때문에(is-A)
//자식 클래스의 객체는 부모 클래스의 자료형인 것처럼 사용할 수 있다.
++추가적으로, 자바에서 만드는 모든 클래스는 Object 클래스를 상속받기 때문에, Object student = new St1();
이런 식으로도 코드를 작성할 수 있다.
위에서 작성한 St1 예시에서 특이하게 인사를 하는 학생이 있다고 가정해보자.
public St2 extends St1 {
void greet() {
System.out.println("안녕하시와요 저는 "+this.name+"입니당");
}
}
public static void main(String[] args) {
St2 st2 = new St2();
st2.setName("닉");
st2.greet();
//안녕하시와요 저는 닉입니당
}
위의 greet 메서드는 상속받은 St1클래스의 greet 메서드와 입출력이 동일한 메서드이며, 더 높은 우선순위를 가지게 되어 기존 클래스의 greet 메서드가 덮어씌워진다. 이를 우리는 메서드 오버라이딩
이라고 부른다.
오버라이딩
은 기존의 메서드를 자식 클래스에서 덮어쓰는 것이라면, 오버로딩
은 새로운 메서드를 추가
하는 것이다.
public St2 extends St1 {
void greet() {
System.out.println("안녕하시와요 저는 "+this.name+"입니당");
}
void sleep(int time) {
System.out.println(this.name+"이 "+time+"분 자는 중입니다.");
}
}
조합
은 기존 클래스가 새로운 클래스의 구성요소로 쓰이는 것
을 말한다.
즉 기존 클래스가 새로운 클래스의 private 필드로 참조되는 것이다.
public class Nurse {
private int injector;
public Nurse(int injector) {
this.injector = injector;
}
public int getInjector() {
return injector;
}
}
public class Doctor {
public void greet() {
System.out.println("안녕하세요~ 구체적으로 어디가 아파서 오셨나요?");
}
}
public class Hospital {
private Nurse nurse; //조합!!
private Doctor doctor;
public Hospital() {
this.nurse = new Nurse(10);
this.doctor = new Doctor();
}
public void greet() {
doctor.greet();
}
public int getNurseInjectorNumber() {
return nurse.getInjector();
}
}
이렇게 사용하게 되면
이는 상속의 단점인 캡슐화(외부에서 내부 속성이나 메서드에 접근하지 못하게 함) 보장 x
를 보완한다.
그렇다면 상속의 이러한 단점에 대해 코드로 알아보자!
// 잘못된 상속의 예시
// 원소의 덧셈 횟수를 구하기 위해 HashSet을 상속받음
public class InstrumentedHashSet<E> extends HashSet<E> {
// The number of attempted element insertions
private int addCount = 0;
public InstrumentedHashSet() {
}
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;
}
}
InstrumentedHashSet<String> s = new InstrumentedHashSet<>();
s.addAll(List.of("Snap", "Crackle", "Pop"));
우리가 이 메서드에서 addAll을 활용해 위의 리스트를 넣어주면 3을 기대하지만 실제로는 6이 출력된다. 왜? addAll 메서드를 실행할 때 위에서 설명했던 오버라이딩 된 add 메서드가 호출되는데 그 안에는 addCount를 증가시켜주는 코드가 있기 때문에 3개의 요소가 또 더해지기 때문이다.
상속도 적절하게 사용되면 개발이 간편해진다는 장점을 갖기 때문에,
is-a
관계라면 상속을 사용 하도록 하자.is-a
관계 = 일반적 개념 - 구체적 개념 간의 관계)조합은 그 외의 has-a
관계에서 사용하도록 한다.
(has-a
관계 = 일반적인 포함 관계)
https://blogs.oracle.com/javamagazine/post/java-inheritance-composition
https://dundung.tistory.com/201
https://wikidocs.net/280