본문 바로가기

프로그래머/Java Managed Programming

[개체지향 프로그래밍] 상속

상속

상속(inheritance)

  • 거의 모든 사람이 OOP의 핵심이라 여기는 특성
    • 초창기 OO에서 가장 중요한 특성이라 여김
    • 현재에도 상속을 지원하지 않으면 OO 언어라고 안 보는 게 보통
  • OOP의 또 다른 매우 중요한 특성인 다형성의 기반

OOP에서의 상속이란?

이미 존재하는 클래스를 기반으로 새 클래스를 만드는 방법

  • 새 클래스는 기존 클래스의 동작과 상태를 그대로 물려 받음(유전)

  • 그 외에 새 클래스만의 동작과 상태를 추가 가능(진화)

  • 물론 이 새 클래스를 상속해서 또 다른 클래스를 만들 수 있음

  • 이미 존재하는 클래스를 부르는 이름

    • 부모(parent) 클래스
    • 기반(base) 클래스
  • 새 클래스를 부르는 이름

    • 자식(child) 클래스
    • 파생(derived) 클래스

두 클래스 간의 상속 관게를 설명하는 표현

  1. 자식 클래스가 부모 클래스를 상속 받았다
  2. 자식 클래스가 부모 클래스로부터 파생되었다
  3. 자식 클래스가 부모 클래스의 한 종류이다 (is-a)

생성자 호출 순서

  1. 메모리에 개체 생성
  2. 부모 생성자 호출
  3. 자식 생성자 호출

Student 생성자에서 Person 생성자 호출하기

public class Person {
    // ...
    public Person(String firstName, String lastName){
        this.firstName = firstName;
        this.lastName = lastName;
    }
    // ...
}

public class Student extends Person {
    // ...
    public Student(String firstName, String lastName){
        super(firstName, lastName);
    }
    // ...
}

super 키워드

부모클래스의 생성자를 호출할 때
super(<매개변수 목록>)

부모 클래스의 멤버 변수/함수를 호출할 때
super.<부모의 멤버 변수/부모의 메서드>

  • super는 현 개체의 부모 부분을 가리킴
  • super()라고 코드를 작성하면 부모의 생성자를 호출
  • 멤버 변수나 메서드를 호출할 때도 사용 가능

접근 제어자: protected

protected <자료형> <변수명>;
protected <반환형> <함수형> (<매개변수 목록>) {...}
protected class <클래스명> { ... }

  • 외부자들은 접근할 수 없음
  • 클래스 내부, 같은 패키지에 속한 클래스, 자식 클래스만 접근 가능
  • 클래스의 경우 내포된(nested) 클래스에 한 해 붙일 수 있음

is-a 관계

  • 상속 관계
  • 수학에서 부분 집합 관계

has-a 관계

  • 컴포지션 관계

상속 vs 컴포지션

  • 둘다 재사용성을 위한 방법
  • 상속으로 해결할 수 있는 많은 문제를 컴포지션으로도 가능
    • 그 반대도 가능
    • 순전히 기술적인 관점
  • 역사적으로 사람들의 선호는 왔다 갔다
    • 초창기에는 상속을 과도하게 선호
    • 그 후 무조건 컴포지션이 답이라는 잘못된 조언을 많이 따랐음(현재 진행형)
  • OO에서 큰 결정사항 중 하나: 상속 vs 컴포지션 중 하나를 고르는 것
  • 실생활에서 개첻르끼리의 관계를 기준으로 선택할 것
    • has-a 관계: 컴포지션
    • is-a 관계: 상속

부모를 자식에게 대입할 수 있게 되면...

public static Student = convertToStudent(Person person){
    Student student = person;

    return student;
}

// 다른 함수 어딘가
convertToString(student);
convertToString(teacher);
convertToString(partTime);
  • 메서드 호출은 호출자의 마음!
  • Person 및 Person의 자식 클래스를 모두 전달할 수 있음
  • 즉, 실행 중에 반드시 Student 개체만 들어온다는 보장이 없음

자식을 부모에 대입하는 건 암시적 캐스팅

  • 부모 <- 자식은 사실 캐스팅
  • 이걸 컴파일러가 암시적으로 해준 것일 뿐
Student student = new Student("Leon", "Kim");
Person person = (Person) student;

// person 생략
Person person = (Person) student;

그 반대 캐스팅은 반드시 명시적으로

  • 자식 <- 부모는 명시적 캐스팅으로만 가능
Student student = new Student("Leon", "Kim");
Person person = student;

// person 생략
Stduent actuallyStudent = (Student) person;
  1. 부모를 자식으로 캐스팅 후 호출
  2. 컴파일 잘 됨

전혀 상관없는 클래스로 캐스팅하면?

  • 컴파일러가 잡아줌

컴파일러가 못 잡아내는 경우

Person person = new Student("Leon", "Kim");
Teacher teacher = (Teacher) person;
  • 이럴 때는 실행 중에 예외 발생
    • ClassCastException

우리가 작성하고 싶은 로직

Person person = new Student("Leon", "Kim");
Teacher teacher = (Teacher) person;
  1. 이 Person 개체가 실제로는 Teacher인지 확인
  2. 실제로 Teacher인 경우에만 Teacher로 캐스팅

    결국 부모형 변수에 저장된 개체가 실제 어떤 자식형인지 알 방법이 필요
    그것도 실행 중에!
    이걸 RTTI(run time type identification) 기능이라고 함

instanceof 연산자

<변수명> instance of <클래스명>

Person person = new Student("Leon", "Kim");

System.out.printIn(person instanceof Student); // true 출력
System.out.printIn(person instanceof Teacher); // false 출력
  • 개체가 특정 클래스의 인스턴스인지 판단하는 연산자

instanceof를 사용해서 고친 코드

Person person0 = new Student("Leon", "Kim");
Person person1 = new Teacher("Pope", "Kim", Department.COMPUTER_SCIENCE);

Teacher teacher = null;
if(person0 instanceof Teacher) {
    teacher = (Teacher) person0;    // teacher: null
}

if(person1 instanceof Teacher) {
    teacher = (Teacher) person1;    // teacher: person1
}

instanceof 연산자는 반드시 '특정 클래스'의 인스턴스를 확인하는 게 아님
부모 클래스로 검사해도 true 반환

getClass()

<변수명>.getClass()

FullTimeTeacher teacher = new FullTimeTeacher("Lulu", "Choi", Department.MAGIC);
Class c = teacher.getClass();
  • 실행 중에 개체의 클래스 정보를 얻어올 수 있음
  • 반환된 개체(Class)에는 여러 유용한 메서드가 들어 있음

getClass().getName()

<변수명>.getClass().getName()

FullTimeTeacher teacher = new FullTimeTeacher("Lulu", "Choi", Department.MAGIC);
Class c = teacher.getClass().getName(); // academy.pocu.FullTimeTeacher
  • 클래스명을 반환하는 메서드
  • 이 때, 클래스명은 패키지 경로까지 포함

언제 사용?

  • getClass()를 편하게 사용할 수 있는 경우가 종종 있음
    • 주로 클래스 이름을 찾을 때
    • 클래스 안에 있는 메서드나 멤버 변수 등도 찾을 수 있음
  • getClass().getName()은 정말 많이 사용
    • 예: 로그 메시지를 출력할 때

좋아 보이는 RTTI 그러나...

  • 매니지드 언어들은 보통 RTTI를 지원
  • 단, 그만큼 실행 중에 뭔가 더 해야 함
  • 성능 또는 메머리가 중요한 경우에는 별로인 기능
  • C/C++ 등의 언어에서는 RTTI 지원이 없거나 사용을 안 함

Object 클래스

  • Java의 모든 클래스는 Object라는 클래스를 상속
  • 즉, 앞에서 Person[]에 넣었던 개체를 Object[]에도 넣을 수 있음
  • 모든 클래스는 Object를 상속 받으니 그 메서드들도 같이 딸려옴
  • Object에는 유용한 메서드들이 좀 있음
  • RTTI도 그 중 하나