com.eomcs.oop.ex05 상속
# 기능 추가하기
1) 기존 클래스에 코드를 추가하는 방법
- 기존 코드를 변경하게 되면 원래 되던 기능도 오류가 발생할 수 있는 위험이 있다.
- 그래서 원래 코드를 손대는 것은 매우 위험한 일이다.
- 기존에 잘 되던 기능까지 동작이 안되는 문제가 발생할 수 있기 때문이다.
2) 기존 코드를 복사하여 새 클래스를 만드는 방법
- 장점
- 기존 코드를 손대지 않기 때문에 문제가 발생할 가능성은 줄인다.
- 단점
- 기존 코드의 크기가 큰 경우에는 복사 붙여넣기가 어렵다.
- 기존 클래스의 소스가 없는 경우에는 이 방법이 불가능하다.
엥? 다른 개발자가 배포한 라이브러리만 있는 경우를 말한다.
소스가 없는 다른 개발자가 만든 클래스에 기능을 덧 붙일 때는 이 방법이 불가능하다.
- 기존 코드에 버그가 있을 때 복사 붙여넣기 해서 만든 클래스도 영향을 받는다.
- 기존 코드를 변경했을 때 복사 붙여넣기 한 모든 클래스를 찾아 변경해야 한다.
3) 기존 코드를 상속 받아 기능을 추가하는 방법
- 장점
- 기존 클래스의 소스 코드가 필요 없다.
- 간단한 선언으로 상속 받겠다고 표시한 후 새 기능만 추가하면 된다.
- 단점
- 일부 기능만 상속 받을 수 없다.
- 쓰든 안쓰든 모든 기능을 상속 받는다.
// 상속 - 상속하지 않고 기능 추가 I
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
package com.eomcs.oop.ex05.c;
public class Exam01 {
public static void main(String[] args) {
// 새 프로젝트에서는 제조사, 모델명, 수용인원 외에
// 썬루프 장착여부, 자동변속 여부를 추가적으로 저장하고 싶다!
Car2 c = new Car2("비트자동차", "티코", 5, true, true);
}
}
// 방법2) 기존 코드를 복사하여 새 클래스(Car2)를 만든다.
// 문제점:
// => 같은 일을 하는 여러 클래스가 존재(코드가 중복됨)하게 되면 관리하기가 힘들다!
// => 만약 원본 코드에 버그가 있으면 버그도 복사하게 된다.
// 따라서 버그를 고칠 때는 복사한 모든 소스 파일을 찾아 고쳐야 한다.
// 상속 - 상속 문법을 이용한 기능 추가
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();
이렇게 표현해주어야한다.
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 클래스를 말한다.
//
// 즉 수퍼 클래스나 서브 클래스는 상대적인 개념이다.
// 상속 - 생성자 호출 순서
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 클래스의 생성자를 실행한다.
// 상속 - 다중 상속
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 메모리를 만들어야 하는가?
// 메서드의 경우도 마찬가지이다.
// 다중 상속이 가능하다고 가정하자.
// 두 개의 수퍼 클래스에 같은 이름의 메서드가 있을 때,
// 어떤 메서드를 호출해야 하는가?
// 그래서 자바는 클래스의 다중 상속을 지원하지 않는다.
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 클래스명 {...}
//
// 상속 - 추상 메서드
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 연산자
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 이 모두 포함된다.