본문 바로가기

자바

[Java] 상속에 대하여

2021-03-04글

강의시간에 배운 상속 개념에 추가로 공부한 내용을 정리해본다 ✍️

상속

먼저 상속이란 ?
기존의 클래스를 재사용하여 새로운 클래스를 작성하는 것이다.
예제를 보명서 상속의 필요성을 파악해보자 !

Example

카페에서 커피와 차를 판매한다.
커피와 차를 준비하는 과정을 살펴보자.

커피

  • 물을 끓인다.
  • 커피머신으로 커피를 내린다.
  • 컵에 붓는다.
  • 설탕과 우유를 추가한다.

  • 물을 끓인다.
  • 차 티백을 담근다.
  • 컵에 붓는다.
  • 레몬을 추가한다.

그렇다면 각각의 과정들을 코드로 구현해본다.

public class Coffee {
    void prepareRecipe() {
        boilWater();
        brewCoffeeMachine();
        pourInCup();
        addSugarAndMilk();
    }

    public void boilWater() {
        System.out.println("물을 끓인다.");
    }

    public void brewCoffeeMachine() {
        System.out.println("커피머신으로 커피를 내린다.");
    }

    public void pourInCup() {
        System.out.println("컵에 붓는다.");
    }

    public void addSugarAndMilk() {
        System.out.println("설탕과 우유를 추가한다.");
    }
}
public class Tea {
    void prepareRecipe() {
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }

    public void boilWater() {
        System.out.println("물을 끓인다.");
    }

    public void steepTeaBag() {
        System.out.println("티백을 담근다.");
    }

    public void pourInCup() {
        System.out.println("컵에 붓는다.");
    }

    public void addLemon() {
        System.out.println("레몬을 추가한다.");
    }
}

Coffee 클래스와 Tea 클래스를 비교해보면 이상한 점을 느낄 수 있다.
prepareRecipe(), boilWater(), pourInCup()
이 세가지 메소드는 공통된 기능을 하지 않는가??

그렇다면 "이 중복된 부분을 어떻게 제거하느냐?"에 대한 해답으로
바로 상속이 나온다.

그렇다면 두 클래스에서 공통된 부분을 추출하여 부모 클래스를 만들어본다.

public class Beverage {
    protected void boilWater() {
        System.out.println("물을 끓인다.");
    }

    protected void pourInCup() {
        System.out.println("컵에 붓는다.");
    }
}

공통된 기능을 묶은 부모 클래스인 Beverage를 상속 받는 각각의 클래스를 만들어본다.

public class Coffee extends Beverage {
    void prepareRecipe() {
        boilWater();
        brewCoffeeMachine();
        pourInCup();
        addSugarAndMilk();
    }

    public void brewCoffeeMachine() {
        System.out.println("필터를 활용해 커피를 내린다.");
    }

    public void addSugarAndMilk() {
        System.out.println("설탕과 우유를 추가한다.");
    }
}
public class Tea extends Beverage {
    void prepareRecipe() {
        boilWater();
        steepTeaBag();
        pourInCup();
        addLemon();
    }

    public void steepTeaBag() {
        System.out.println("티백을 담근다.");
    }

    public void addLemon() {
        System.out.println("레몬을 추가한다.");
    }
}

extends

이렇게 상속은 extends 키워드로 이루어 진다.
상속을 할 경우 멤버 필드와 메소드를 하위 클래스에서 그대로 상속하게 되는데,
키워드의 의미대로 자식 클래스가 조상 클래스를 확장하는 의미로 해석할 수 있다.
여기서 주의점 !

  • 생성자와 초기화 블럭은 상속되지 않는다. 멤버만 상속된다.
  • 자식 클래스의 멤버 개수는 조상 클래스보다 항상 같거나 많다.
  • private, default는 상속되지 않는다기 보다는 상속은 받지만 자식 클래스로부터의 접근이 제한된다.
  • 자바에서는 다중 상속을 지원하지 않는다.

오버라이딩

조상 클래스로부터 상속 받은 메서드의 내용을 변결하는 것
자식 클래스에서 부모 클래스 메서드를 맞게 변경하는 경우 오버라이딩을 한다.

오버라이딩의 조건

자식 클래스의 메소드에서 부모 클래스의 메소드와 다음 조건들이 같아야 한다.

  • 메소드 명
  • 매개변수
  • 반환타입

    오버라이딩 주의점

  • 접근 제어자를 조상 클래스의 메서드보다 좁은 범위로 변경할 수 없다.
  • 예외는 조상 클래스의 메서드보다 많이 선언할 수 없다.
  • 인스턴스메서드를 static 메서드, 또는 그 반대로 변경할 수 없다.

🤔 조상 클래스에 정의된 static 메서드를 자손 클래스에서 똑같은 이름의 static 메서드로 정의할 수 있나?
가능은 하지만 각 클래스에 별개의 static 메서드를 정의한 것일 뿐 오버라이딩이 아니다.

상속의 장점

  • 중복된 코드를 줄일 수 있다.
  • 공통적 필드를 추가하거나, 수정할 때 부모 클래스에만 작업해주면 된다.

클래스간의 관계

상속을 통해 클래스간에 관계를 맺어주는데,
이때 생기는 관계를 몇 가지 유형으로 나눠볼 수 있다.

is-a 관계 (is a relationship)

is-a 관계는 일반적인 개념과 구체적인 개념과의 관계를 말한다.
여기서 일반적인 개념 클래스는 상위 클래스인 부모 클래스를 뜻하고,
구체적인 개념 클래스는 하위 클래스인 자식 클래스를 뜻한다.

말 그대로 "~은 ~이다."라는 문장이 성립되어야 한다.

  • Example
    public class Person{
    String name; 
    int age; 
    }
    

public class Student extends Person{
int number;
int major;
}

위 예제에서는 Student 클래스가 Person 클래스를 상속받고 있다.
"학생은 사람이다."처럼 이러한 관계를 is-a 관계라고 한다.

#### has-a 관계 (composition)
has-a 관계는 한 클래스가 다른 클래스를 소유한 관계이다.

말 그대로 **"~은 ~을 가지고 있다."라는 문장이 성립되어야 한다.**
* Example
```java
class Circle {
    Point point;
    int r;
} 

class Point {
    int x;
    int y;
}

위 예제에서는 Point 클래스를 재사용해서 Circle 클래스를 작성했다.
하나의 거대한 클래스를 작성하는 것보다 단위별로 여러 개의 클래스를 작성한 후,
이 단위 클래스들을 포함관계로 재사용하면 보다 간결하고 손쉽게 클래스를 작성할 수 있다.
또 작성된 단위 클래스들은 다른 클래스를 작성하는데 재사용될 수 있다.

"은 ~이다." 또는 "은 ~을 가지고 있다." 문장을 만들어보고
더 적합한 관계를 구현하는 것이 좋다.

(이펙티브 자바에 "상속보단 조합"이라는 개념이 있던데 이 부분도 정리해보아야겠다.)

캐스팅

🤔 캐스팅이란 ?
타입을 변환하는 것을 말하며, 형변환이라고도 한다.
기본형 변수와 같이 참조변수도 형변환이 가능한데, 상속 관계에 있는 클래스 사이에서만 가능하다.

이때, 기본형 참조형 변수의 형변환에서 자손 타입의 참조변수를 조상타입으로
형변환하는 경우에는 형변환을 생략할 수 있다.

업캐스팅

자식 클래스의 참조변수를 부모 클래스의 참조변수로 변환하며, 형변환 생략이 가능하다.
부모 클래스의 참조 변수가 자식 클래스로 객체화된 인스턴스를 가리킬 수 있다.
때문에 다형성을 위해 사용된다.
ex) 학생은 사람이다.

위에서 다뤘던 Student 예제로 확인해보자.

// 참조변수 student를 이용하면 number, major에 접근 가능
Student student = new Student();

// 레퍼런스 person을 이용하면 Student 객체의 멤버 중 
// 오직 Person 클래스의 멤버만 접근이 가능하다.
Person person = student;
person.name = "amazzi";
person.age = 10;

// 아래 문장은 컴파일 오류가 난다.
person.number = "12345678";

이처럼 업캐스팅을 하게되면 부모 클래스의 필드, 메서드에만 접근이 가능하다.

다운캐스팅

자식 클래스의 참조변수를 자식 타입의 참조변수로 변환하며, 형변환 생략이 불가능하다.
자신의 고유한 특성을 잃은 서브 클래스의 객체를 다시 복구 시켜주는 것을 말한다. (업캐스팅 복구?)

// 업캐스팅 먼저
Person person = new Student();

// 다운캐스팅
Student student = (Student) person;

// Ok
student.name = "amazzi";
student.number = "12345678";

꼭 업캐스팅이 먼저 선행이 되어야 한다.
만약 다음과 같은 경우는 컴파일 오류는 나지 않지만 ClassCastException 런타임 오류가 발생한다.

Student student = (Student) new Person();

'자바' 카테고리의 다른 글

[Java] Functional Interface  (0) 2021.08.06
[Java] 추상 클래스와 인터페이스의 차이  (0) 2021.08.06
[Java] BigDecimal에 관한 고찰 🕵️‍♀️  (0) 2021.08.06
[Java] Stream 부수기  (0) 2021.08.06
📖 Exception에 대하여  (0) 2021.08.02