🧑‍💻복습/자바

자바 복습 : oop.ex05 상속

우동한그릇 2023. 6. 22. 12:11
반응형

com.eomcs.oop.ex05  상속

 

# 기능 추가하기
1) 기존 클래스에 코드를 추가하는 방법
  - 기존 코드를 변경하게 되면 원래 되던 기능도 오류가 발생할 수 있는 위험이 있다.
  - 그래서 원래 코드를 손대는 것은 매우 위험한 일이다.
  - 기존에 잘 되던 기능까지 동작이 안되는 문제가 발생할 수 있기 때문이다.

2) 기존 코드를 복사하여 새 클래스를 만드는 방법
  - 장점
    - 기존 코드를 손대지 않기 때문에 문제가 발생할 가능성은 줄인다.


  - 단점
    - 기존 코드의 크기가 큰 경우에는 복사 붙여넣기가 어렵다.
    - 기존 클래스의 소스가 없는 경우에는 이 방법이 불가능하다.
      엥? 다른 개발자가 배포한 라이브러리만 있는 경우를 말한다.
      소스가 없는 다른 개발자가 만든 클래스에 기능을 덧 붙일 때는 이 방법이 불가능하다.
    - 기존 코드에 버그가 있을 때 복사 붙여넣기 해서 만든 클래스도 영향을 받는다.
    - 기존 코드를 변경했을 때 복사 붙여넣기 한 모든 클래스를 찾아 변경해야 한다.


3) 기존 코드를 상속 받아 기능을 추가하는 방법
  - 장점
    - 기존 클래스의 소스 코드가 필요 없다.
    - 간단한 선언으로 상속 받겠다고 표시한 후 새 기능만 추가하면 된다.


  - 단점
    - 일부 기능만 상속 받을 수 없다.
    - 쓰든 안쓰든 모든 기능을 상속 받는다.

 


// 상속 - 상속하지 않고 기능 추가 I

com.eomcs.oop.ex05.b

package com.eomcs.oop.ex05.b;

public class Exam01 {
  public static void main(String[] args) {
    // 새 프로젝트에서는 제조사, 모델명, 수용인원 외에
    // 썬루프 장착여부, 자동변속 여부를 추가적으로 저장하고 싶다!
    Car c1 = new Car("비트자동차", "티코", 5, true, true);
  }
}

    // 방법1) 기존의 Car 클래스를 변경한다
    //       문제점:
    //       => 기존 소스를 변경하게 되면 기존의 Car를 사용해서 만든 프로그램들도
    //          영향을 받는다.
    //       => 심각한 오류가 발생할 수 있다.
    //       => 그래서 기존의 소스 코드를 손대는 것은 매우 위험하다.
    //       => 그리고 계속 코드를 덧 붙이다 보면 누더기 코드가 될 가능성이 있다.
    //       => 쓰지도 않는 코드가 계속 누적되는 문제가 있다.




 

 

// 상속 - 상속하지 않고 기능 추가 II

com.eomcs.oop.ex05.c

package com.eomcs.oop.ex05.c;

public class Exam01 {
  public static void main(String[] args) {
    // 새 프로젝트에서는 제조사, 모델명, 수용인원 외에
    // 썬루프 장착여부, 자동변속 여부를 추가적으로 저장하고 싶다!
    Car2 c = new Car2("비트자동차", "티코", 5, true, true);
  }
}

    // 방법2) 기존 코드를 복사하여 새 클래스(Car2)를 만든다.
    //       문제점:
    //       => 같은 일을 하는 여러 클래스가 존재(코드가 중복됨)하게 되면 관리하기가 힘들다!
    //       => 만약 원본 코드에 버그가 있으면 버그도 복사하게 된다.
    //          따라서 버그를 고칠 때는 복사한 모든 소스 파일을 찾아 고쳐야 한다.

 

 


 

 

// 상속 - 상속 문법을 이용한 기능 추가

com.eomcs.oop.ex05.d

package com.eomcs.oop.ex05.d;

public class Exam01 {
  public static void main(String[] args) {
    // 새 프로젝트에서는 제조사, 모델명, 수용인원 외에 
    // 썬루프 장착여부, 자동변속 여부를 추가적으로 저장하고 싶다!
    Sedan c = new Sedan("티코", "비트자동차", 5, true, true);
  }
}

    // 방법3) 상속을 이용하여 기능을 추가한 클래스를 사용한다.
    //       장점:
    //       => 기존 코드에 문제가 있으면 그 코드를 수정하는 순간 
    //          그 코드를 상속 받아 만든 모든 클래스에 자동으로 적용된다.
    //       => 기존 코드를 손대지 않기 때문에 새 기능을 추가하더라도
    //          기존 기능에 문제가 발생할 가능성이 거의 없다.
    //       => 소스 코드의 유지보수가 쉽다.
    // 이것이 상속이라는 문법이 등장한 이유이다.
    // => 즉 기존 코드의 수정을 최소화하면서 새 기능을 추가하는 방법!
    // => 기존 기능을 재작성하지 않고 다시 사용할 수 있게 만드는 문법이다.
    // => 코드 재사용성을 높인다. 

 


* 상속과 객체 및 인스턴스 변수 사용

단 상속을 받아 생성한 객체가 인스턴스 변수를 사용하기위해서는

컴파일러가 인식할 수 있게 인스턴스 변수가 어디에 있는 지

클래스를 알려주어야한다.

 

ex) 

B obj = new D();

D의 설계도를 바탕으로 B의 객체를 생성하게되면,

B는 D의 부모 클래스라서 가능하다.

 

하지만 B는 상속받은 A의 인스턴스 변수를 갖고 있지만

obj.m1()

C와 D에 대한 인스턴스 변수가 없기 때문에 

((D)obj).m4();

이렇게 표현해주어야한다.

 


com.eomcs.oop.ex05.f

// 상속 - 클래스 로딩과 인스턴스 생성 과정

package com.eomcs.oop.ex05.f;

public class Exam01 {
  public static void main(String[] args) {
    // B 클래스의 설계도에 따라 Heap 영역에 변수를 준비한다.
    // - B 클래스는 A 클래스에 기능을 덧붙인 것이라 선언했기 때문에
    //   A 클래스의 설계도에 따라 A 클래스에 선언된 인스턴스 변수도 함께 생성된다.
    B obj = new B();

    obj.v2 = 200; // B 클래스 설계도에 따라 만든 변수
    obj.v1 = 100; // A 클래스 설계도에 따라 만든 변수

    System.out.printf("v2=%d, v1=%d\n", obj.v2, obj.v1);
    System.out.println("---------------------------------");

    // 클래스는 오직 한 번만 로딩된다.
    // - 그래서 static 블록도 위에서 한 번 실행되면 다시 실행하지 않는다.
    //
    B obj2 = new B();
    obj2.v2 = 2000;
    obj2.v1 = 1000;
    System.out.printf("v2=%d, v1=%d\n", obj2.v2, obj2.v1);

   
  }
}

 

 

 

// B 클래스의 인스턴스 생성 과정
    // 1) B의 수퍼 클래스가 로딩되어 있지 않다면, 수퍼 클래스(A 클래스)를 먼저 로딩한다.
    //    - 스태틱 필드 생성한 후
    //    - 스태틱 블록 실행한다.
    // 2) 그런 후 B 클래스를 로딩한다.
    //    - 스태틱 필드 생성한 후
    //    - 스태틱 블록 실행한다.
    // 3) 인스턴스 필드 생성
    //    - 수퍼 클래스의 인스턴스 필드부터 생성한다.
    //    v1   |    v2    : A의 v1 필드 생성, B의 v2 필드 생성
    //    0    |    0     : 각 필드를 기본 값으로 설정한다.
    //    100  |    0     : A 클래스의 생성자 수행
    //    100  |    200   : B 클래스의 생성자 수행
    //
    // - B 클래스의 인스턴스는 수퍼 클래스의 인스턴스 필드도 포함한다.
    //

    // 인스턴스 생성 절차 정리!
    // 1) 상속 받은 수퍼 클래스를 먼저 메모리에 로딩한다.
    //    이미 로딩되어 있다면 다시 로딩하지는 않는다.
    // 2) 그런 후 해당 클래스를 메모리에 로딩한다.
    //    마찬가지로 이미 로딩되어 있다면 다시 로딩하지는 않는다.
    // 3) 수퍼 클래스에 선언된 대로 인스턴스 변수를 Heap에 만든다.
    // 4) 해당 클래스에 선언된 대로 인스턴스 변수를 Heap에 만든다.
    // 5) 수퍼 클래스부터 생성자를 실행하며 해당 클래스까지 내려온다.

    // 그래서 인스턴스를 생성할 때는 항상 상속 받아야 하는 클래스 파일이 모두 있어야 한다.
    // 테스트 하는 방법?
    // => A.class 파일을 제거하고 실행해 보라!

 


// 용어 정리!
// 상속(inheritance)
// - 기존에 만든 클래스를 자신의 코드처럼 사용하는 기법이다.
// - 보통 기존 코드를 손대지 않고 새 코드를 덧붙일 때 많이 사용한다.
//
// 수퍼클래스(super class) = 부모클래스(parents class)
// - B 클래스 입장에서, B 클래스에게 상속 해주는 A 클래스를 말한다.
//
// 서브클래스(sub class) = 자식클래스(child class)
// - A 클래스 입장에서, A 클래스가 상속해주는 B 클래스를 말한다.
//
// 즉 수퍼 클래스나 서브 클래스는 상대적인 개념이다.


com.eomcs.oop.ex05.g

// 상속 - 생성자 호출 순서

package com.eomcs.oop.ex05.g;

public class Exam01 {
  public static void main(String[] args) {
    C obj = new C();
    System.out.printf("v1=%d, v2=%d, v3=%d\n", obj.v1, obj.v2, obj.v3);
  }
}

    // 생성자 호출 순서
    // 1) C 클래스의 생성자를 호출하면,
    //    그 생성자의 첫 번째 문장이 수퍼 클래스의 생성자를 호출하는 명령이다.
    //    그래서 수퍼 클래스인 B 클래스의 생성자를 호출한다.
    // 2) B 클래스의 생성자를 호출하면,
    //    그 생성자의 첫 번째 문장이 수퍼 클래스의 생성자를 호출하는 명령이다.
    //    그래서 수퍼 클래스 A의 생성자를 호출한다.
    // 3) A 클래스의 생성자를 호출하면,
    //    그 생성자의 첫 번째 문장이 수퍼 클래스의 생성자를 호출하는 명령이다.
    //    그래서 수퍼 클래스 Object의 생성자를 호출한다.
    // 4) Object 클래스의 생성자를 호출하면,
    //    더이상 수퍼 클래스가 없기 때문에 Object() 생성자를 실행한다.
    //    그리고 이 생성자를 호출한 A 클래스의 생성자로 리턴한다.
    // 5) A 클래스의 생성자를 실행한 후
    //    이 생성자를 호출한 B 클래스의 생성자로 리턴한다.
    // 6) B 클래스의 생성자를 실행한 후
    //    이 생성자를 호출한 C 클래스의 생성자로 리턴한다.
    // 7) C 클래스의 생성자를 실행한다.


com.eomcs.oop.ex05.i

// 상속 - 다중 상속

package com.eomcs.oop.ex05.i;

public class Exam01 {

  public static void main(String[] args) {

    C obj = new C(); // ?


  }

}

    // 만약 C 클래스가 A와 B를 모두 상속 받을 수 있다면,
    // C 클래스의 인스턴스를 만들 때 
    // v2 변수는 A 설계도에 따라 float 메모리를 만들어야 하는가?
    // 아니면 B 설계도에 따라 int 메모리를 만들어야 하는가?
    // 메서드의 경우도 마찬가지이다.
    // 다중 상속이 가능하다고 가정하자.
    // 두 개의 수퍼 클래스에 같은 이름의 메서드가 있을 때,
    // 어떤 메서드를 호출해야 하는가?
    // 그래서 자바는 클래스의 다중 상속을 지원하지 않는다.


com.eomcs.oop.ex05.i

package com.eomcs.oop.ex05.m;

public abstract class Car {

  public Car() {
    super();
  }

  public void start() {
    System.out.println("시동 건다!");
  }

  public void shutdown() {
    System.out.println("시동 끈다!");
  }

  public void run() {
    System.out.println("달린다.");
  }

}


// 이렇게 Sedan과 Truck의 경우처럼
// 여러 클래스의 공통점을 추출하여 수퍼 클래스를 정의하는 경우,
// 그 수퍼 클래스의 목표는 서브 클래스의 공통 기능을 물려주는 것이다.
// 처음부터 Car를 먼저 만들어 쓰다가 더 특별한 클래스가 필요해서
// Sedan이나 Truck을 만든 것이 아니라,
// 여러 클래스를 사용하다 보니 공통 분모가 보여서,
// 소스 코드의 유지보수가 쉽도록
// 한 클래스로 모아두기 위해 만든 경우는
// 해당 클래스를 직접 사용할 이유가 없다.
//
// 특히 generalization을 수행하여 만든 수퍼 클래스의 경우
// 직접 사용할 목적으로 만든 클래스가 아니다.
// 단지 서브 클래스에 공통 기능을 물려주기 위해 존재하는 클래스이다.
// 이런 클래스들은 직접 사용하지 못하게 막는 것이 좋다.
//
// 클래스를 직접 사용하지 못하게 막고
// 단지 서브 클래스를 만들어 사용하도록 제한하는 문법이
// "추상 클래스" 이다.
//
// 추상 클래스
// => 서브클래스에게 공통 기능을 상속해주는 목적으로 만든 클래스이다.
// => 직접 사용하지 않는 클래스이다.
// => 즉 개발자에게 이 클래스를 상속 받아 새 클래스를 만들어 쓰라는 의미다!
// => 보통 '일반화(generalization)' 과정에서 생성되는 클래스를 추상 클래스로 만든다.
// => 문법:
//      abstract class 클래스명 {...}
//


com.eomcs.oop.ex05.n

// 상속 - 추상 메서드

package com.eomcs.oop.ex05.n;

// Car 클래스의 start()와 shutdown()은 서브 클래스에서 그대로 받아 사용해도 된다.
// 그러나 run() 메서드는 서브 클래스마다 자신의 특징에 맞춰 재정의해야 한다.
// 그렇다면 굳이 수퍼 클래스에서 run() 메서드를 구현할 필요가 없지 않는가?
// run()처럼 서브 클래스에서 무조건 재정의되어야 하는 메서드인 경우
// 즉 수퍼 클래스에서 정의하지 않고 서브클래스에 반드시 정의하도록 강제하는 문법이 
// "추상메서드(abstract method)"이다.
//
public class Exam01 {

  public static void main(String[] args) {
    Sedan car1 = new Sedan();
    car1.run();
  }

}

단, 수퍼클래스에서 abstract 메소드가 있다면 반드시 서브클래스가

해당 시그니처를 가진 메서드가 존재해야한다.

왜냐하면 수퍼클래스는 추상 메서드를 갖고 있기 때문이다.


// 다형성 - 다형적 변수와 instanceof 연산자

com.eomcs.oop.ex05.a

package com.eomcs.oop.ex06.a;

public class Exam0510 {

  public static void main(String[] args) {
    Vehicle v = new Sedan();


    System.out.println(v instanceof Sedan);
    System.out.println(v instanceof Car);
    System.out.println(v instanceof Vehicle);
    System.out.println(v instanceof Object);

    System.out.println(v instanceof Truck);
    System.out.println(v instanceof Bike);
  }

}

    // instanceof 연산자?
    // => 레퍼런스에 들어있는 주소가 특정 클래스의 인스턴스인지 검사한다.
    // => 또는 그 상위 | 상속받는 하위 클래스의 인스턴스인지 검사한다.

 

// 다형성 - 다형적 변수의 활용
package com.eomcs.oop.ex06.a;

public class Exam0521 {

  public static void main(String[] args) {
    // 수퍼 클래스의 레퍼런스는 하위 클래스의 인스턴스를 담을 수 있다.
    Vehicle[] arr = new Vehicle[] {
        new Car("비트자동차", 5, 1980, 16), 
        new Bike("캠프모터", 5, true), 
        new Sedan("현대자동차", 5, 1980, 16, true, true), 
        new Truck("기아자동차", 5, 10000, 32, 15f, true)};

    for (int i = 0; i < arr.length; i++) {
      printCar(arr[i]);
    }
  }

  static void printCar(Vehicle v) {
    System.out.printf("모델: %s\n", v.model);
    System.out.printf("수용인원: %s\n", v.capacity);

    if (v instanceof Car) {
      Car c = (Car) v;
      System.out.printf("cc: %s\n", c.cc);
      System.out.printf("밸브: %s\n", c.valve);

      if (v instanceof Sedan) {
        Sedan s = (Sedan) v;
        System.out.printf("썬루프: %b\n", s.sunroof);
        System.out.printf("오토: %b\n", s.auto);
      } else if (v instanceof Truck) {
        Truck t = (Truck) v;
        System.out.printf("톤: %f\n", t.ton);
        System.out.printf("덤프여부: %b\n", t.dump);
      }
    } else if (v instanceof Bike) {
      Bike b = (Bike) v;
      System.out.printf("engine: %b\n", b.engine);
    }
    System.out.println("-----------------------");
  }

}

 

첫째로 sedan 일 경우에는 해당 클래스 sedan과 상위클래스인 car, vehicle, object가 True이다.

두번째 예제로 vehicle 일 경우에는 vehicle과 하위 클래스인 bike, car, sedan, truck 이 모두 포함된다.

반응형