상속(Inheritance)

임준철·2021년 4월 11일
0

OOP

목록 보기
2/6

상속(Inheritence)

  • 상속과 다형성을 이해를 잘하면 유지보수하기 쉽고 확장성있는 시스템을 설계할 수 있다.
    객체지향의 큰 특징 중 하나이다.

상속이란

  • 어떤 클래스의 모든 멤버 변수 및 메소드를 계승하여, 새로운 클래스를 생성하는 것
  • 부모 클래스로부터 상속을 받은 자식 클래스는 부모의 것을 다 가지고 자기의 것을 추가로 더 가지고 만들어진다.
  • 상속 관계를 흔히 'IS-A'관계라고 한다.

  • 상속대상은 조상클래스, 부모클래스, 상위클래스, 슈퍼클래스 라고 한다.
  • 상속결과는 자손클래스, 자식클래스, 하위클래스, 서브클래스 라고 한다.

상속을 사용하는 경우

  • 상속은 좀 더 일반적인 클래스가 있고 이것보다 좀 더 기능이 구체적인 클래스를 설계 한다.
    유사한 클래스를 만드는데 기존의 클래스를 가져다가 좀 더 확장된 클래스를 만들 때 상속을 사용 한다.

상속에서 접근 제한

  • protected 예약어
    • 클래스에서 멤버변수나 메소드를 private 예약어로 지정해 놨을 때 외부 클래스에서 접근을 하지 못한다.
      그래서 상속 관계에서도 접근을 못하게 되는데 protected 예약어를 사용하면 상위클래스에 protected로 선언된 변수나 메소드를
      다른 외부 클래스에서는 사용할 수 없게 하고 하위클래스에서 사용이 가능하다.

상속에서 클래스 생성 과정과 형변환

  • Customer 상위클래스, VIPCustomer 하위클래스

  • 하위클래스가 생성되는 과정

    • 상위 클래스의 생성자가 호출되고 하위 클래스의 생성자가 호출 된다.
    • 하위 클래스의 생성자에서는 무조건 상위 클래스의 생성자가 먼저 호출되어야 한다.
    • 하위 클래스에서 상위 클래스의 생성자를 호출하는 코드가 없는 경우
      컴파일러는 상위 클래스 기본 생성자를 호출하기 위한 super()를 추가한다.
      • super()로 호출되는 생성자는 상위 클래스의 기본 생성자이다.
    • 만약 상위 클래스의 기본 생성자가 없는 경우(매개변수가 있는 생성자만 존재하는 경우) 하위 클래스는 명시적으로 상위 클래스의 생성자를 호출해야 한다.

상위 클래스의 묵시적 형 변환(업 캐스팅)

  • 상위 클래스 형으로 변수를 선언하고 하위 클래스 인스턴스를 생성 할 수 있다.
    하위 클래스는 상위 클래스의 타입을 내포하고 있으므로 상위 클래스로 묵시적 형 변환이 가능하다.
    상속 관계에서 모든 하위 클래스는 상위 클래스로 묵시적 형 변환이 된다.
    그 반대는 성립하지 않는다.

  • VIPCustomer() 생성자의 호출로 인스턴스는 모두 생성 되었지만,
    타입이 Customer이므로 접근 할 수 있는 변수나 메소드는 Customer의 변수와 메소드이다.

하위 클래스로 명시적 형 변환(다운 캐스팅)

  • 묵시적으로 상위 클래스로 형 변환된 인스턴스가 원래 자료형(하위 클래스)으로 반환되어야 할 때
    다운 캐스팅이라고 한다.
  • 하위 클래스로의 형 변환은 명시적으로 되어야 한다.
  • Customer vc = new VIPCustomer() // 묵시적
  • VIPCustomer vCustomer = (VIPCustomer)vc; //명시적

클래스의 관계

클래스의 상속

  • 부모 클래스
class Person{
    String name;
    public void work(){
        System.out.println("일하기");
    }
    public void sleep(){
        System.out.println("잠자기");
    }
}
  • 자식 클래스
// person을 상속하는 자식 클래스 extends 키워드를 이용하여 상속
class Developer extends Person{
    String mainLang;
    public  void  writeCode(){
        System.out.println("돈 받은 만큼 코딩하기");
    }
}
class Student extends Person{
    String major;

    public void writeCode(){
        System.out.println("밤새 코딩하기");
    }
}

클래스의 포함

  • 관계 클래스를 조합해서 클래스를 만든다.
  • 상속하고 유사하지만, 한 클래스가 다른 클래스의 객체를 포함하는 관계
  • 내부에 포함하고 'HAS-A' 관계로 표현된다.
  • 특별히 자바에선 'HAS-A' 관계가 포함된 경우는 많이 없다.
  • 클래스 컴포지션 (Composition)이라 부른다.
// 메인머신 'HAS-A' String 이미 스트링을 컴포지션하고 있었다.
class MainMachine{
    String model;
    boolean isBroken = false;

    public MainMachine(String model){
        this.model = model;
    }
}
// developer 'HAS-A; MainMachine 개발자클래스가 메인머신의 객체 하나를 보유한다.
// 보유하고 있어서 메인머신의 속성을 건드릴 수 있다.
class Developer{
    String name;
    MainMachine mainMachine;

    public Developer(String name,MainMachine machine){
        this.name = name;
        this. mainMachine = machine;
    }

    public void writeCode(){
        if(mainMachine.isBroken) {
            System.out.println("코딩할 수 없습니다.");
        }else{
            System.out.println(mainMachine.model+ "으로 코딩하기");
        }

        if(Math.random() > 0.9){
            breakMachine();
        }
    }

    public void breakMachine(){
        mainMachine.isBroken = true;
    }
}

MainMachine mac = new MainMachine("MacBook Pro");

    Developer dev = new Developer("나개발",mac); // 메인머신을
//  dev.mainMachine.model 가능
//  dev.model 불가능.
//  일치하냐 보유하냐의 차이 (상속과 컴포지션의 차이)      
    for (int i =0; i<10; i++){
       dev.writeCode();
    }

메소드 재정의

메소드 재정의(Method Overriding)란?

  • Override -> 덮어씌운다, 해킹해서 뭔가 달라지게 한다.
  • 기존의 있던걸 덮어씌워서 다른걸로 변형시키는 것
  • 다형성 (polymorphism)의 근간이 됩니다.
class Person{
    public void writeCode(){
        System.out.println("아무 코드나 일단 적어 보았다.");
    }
}
class Student extends Person{
    @Override // 이렇게 적어주는 것이 관례 필수는 아님(문법 오류는 아님)   @ - 에노테이션
    public void writeCode(){
        System.out.println("능숙하게 코드를 작성해 보았다.");
        System.out.println("답은 틀렸다.");
        System.out.println("하지만 무언가 또 배웠다.");
    }
}

class Developer extends Person{
    @Override // 다양하게 오버라이드 될 수 있음.
    public void writeCode(){
        System.out.println("코드 작성이 하기 싫어서 하지 않았다.");
    }
}

@ 애노테이션(Annotation)

  • 재정의된 메소드라는 의미로 선언부가 기존의 메소드와 다른 경우 에러가 발생한다.(같게 해줘야 함)
  • 애노테이션은 컴파일러에게 특정한 정보를 제공해주는 역할을 한다
    • (컴파일 오류를 막아주고, 컴파일러에게 정보를 전달해줌.)
  • 주로 사용되는 자바에서 제공되는 애노테이션

형변환과 오버라이딩 메소드 호출

Customer vc = new VIPCustomer();
vc.calcPrice(10000);

위 코드에서 calcPrice() 메소드는 어느 메소드에서 호출 될까?
자바에서 항상 인스턴스의 메소드가 호출이 된다. (메모리의 자료형을 따라서 메소드가 호출이된다.)
그 이유는 Virtual method Call이 이루어지기 때문이다.

가상 메소드(Virtual method)

  • 메소드의 이름과 메소드 주소를 가진 메소드 테이블에서 호출될 메소드의 주소를 참조한다.

  • 재정의된 메소드 호출 과정

public class Customer {
    protected int customerID;
    protected String customerName;
    protected String customerGrade;
    int bonusPoint; // 보너스 포인트
    double bonusRatio; //보너스 포인트 적립 비율

    public Customer(){ //생성자를 기본 초기값으로 설정해주듯이 사용하면 될 것 같다.
        customerGrade = "Silver";
        bonusRatio = 0.01; //1%로 적립

        System.out.println("customer 생성자 호출");
    }

    public Customer(int customerID,String customerName){
        this.customerID = customerID;
        this.customerName = customerName;
        customerGrade = "Silver";
        bonusRatio = 0.01; //1%로 적립
        System.out.println("customer 파라미터 생성자 호출");

    }

    public String showCustomerInfo(){
        return customerName+"님의 등급은 "+customerGrade+"이며, 적립된 보너스 포인트는 "+bonusPoint+"점 입니다.";
    }

    public int calcPrice(int price){ //지불할 가격 반환.
        bonusPoint += price * bonusRatio;
        return price;
    }
}
Customer customerLee = new Customer(10010,"이순신");
customerLee.bonusPoint=1000;

VIPCustomer customerKim = new VIPCustomer(10020,"김유신");
customerKim.bonusPoint = 10000;

int priceLee = customerLee.calcPrice(10000);
int priceKim = customerKim.calcPrice(10000);
System.out.println(customerLee.showCustomerInfo() +"지불금액은 "+priceLee);
//이순신님의 등급은 Silver이며, 적립된 보너스 포인트는 1100점 입니다. 지불금액은 10000

System.out.println(customerKim.showCustomerInfo()+"지불금액은 "+priceKim);
//김유신님의 등급은 VIP이며, 적립된 보너스 포인트는 10500점 입니다. 지불금액은 9000

Customer customerNo = new VIPCustomer(10030,"나몰라");
customerNo.bonusPoint=10000;
System.out.println(customerNo.showCustomerInfo()+"지불금액은 "+customerNo.calcPrice(10000));
// 나몰라님의 등급은 VIP이며, 적립된 보너스 포인트는 10000점 입니다. 지불금액은 9000
// 지불금액이 예상하기로 10000이 나와야하지만 결과는 9000이나옴 
// 이유는 vipcustomer의 calcPrice()메소드가 호출되었기 때문에 
// 이것을 가상메소드호출이라 한다.

super 키워드

  • this 가 자신의 객체를 참조하듯, super 는 부모 객체를 참조한다.
  • 다만 super.super 라는 식으로 부모의 부모는 참조할 수 없다. 바로 윗 부모만 가능하다.
  • 자식 객체를 생성을 하면, 부모 객체를 먼저 생성하고, 그 다음에 자식 객체를 생성하게 된다.
  • 자식 객체를 생성할 때마다 부모 객체를 따로따로 만들어서 가지고 있다.
  • 호출할 때 this와 마찬가지로 첫줄에 써야한다.
  • 멤버 변수명이 부모와 겹치면 재정의 하지만, 부모가 사라지는 것은 아니다.
  • 부모클래스에 기본 생성자를 사용하는 경우에는 호출 안해줘도 된다. 파라미터 생성자일 때만
    호출해주면 된다.
  • 부모클래스 생성자와 다르게 파라미터 변수명을 해줘야 한다.
  • 자식클래스 생성자를 만들 때 부모 클래스 생성자도 같이 호출해줘야한다.
class Foo {
    String x = "Foo";

    public Foo(String x) {
        this.x = x;
    }
}
class Bar extends Foo {
    String x = "Bar"; 
    // 멤버 변수명이 부모와 겹치면 재정의 하지만 부모가 사라지는 것은 아님.

    // 부모클래스에 기본 생성자를 사용하는 경우에는 호출 안해주어도 된다.
    // 부모클래스에 파라미터 생성자가 있으면 호출해 줘야 한다. super 사용.
    public Bar(String x, String x1) { 
    // 부모클래스 생성자와 다르게 파라미터 변수명을 다르게 해줘야 한다. x,x1
        super(x); //this와 마찬가지로 첫줄에 써야한다. 부모클래스 생성자 호출.
        //부모 객체를 먼저 생성하고, 그 다음에 자식 객체를 생성.
        this.x = x1; //자식클래스 생성자를 만들때 부모클래스 생성자도 같이 호출해줘야한다.
    }

    public void method(){
        //로컬변수명과 멤버변수명은 같을 수 있다.
    // String x = "method"; 로컬변수가 없어지면 멤버변수를 참조, 
    // 멤버변수도 없어지면 super클래스의 변수사용.
        System.out.println(x);
        // 로컬변수 -> 멤버변수 -> 부모의 멤버변수 순으로 접근
        System.out.println(this.x); 
        // 자기 자신의 객체에 접근가능 멤버변수 - > 부모의 멤버변수
        System.out.println(super.x); // 부모 객체에 접근 가능 부모의 멤버변수
    }
}
profile
지금, 새로운 문을 열자! 문 저편에 무엇이 있을지 두렵더라도!!

0개의 댓글