# 7. 상속
# 7.1 상속 개념
- 상속 : 부모가 자식에게 물려주는 행위
- 객체지향 프로그램에서 상속 : 부모 클래스의 필드와 메소드를 자식 클래스에게 물려줌
- 상속은 이미 잘 개발된 클래스를 재사용 해서 새로운 클래스를 만들기 때문에 중복되는 코드를 줄여 개발 시간을 단축시킨다
- 상속은 클래스 수정을 최소화 할 수 있다. 부모 클래스를 수정하면 모든 자식 클래스에 수정 효과를 가져온다.
# 7.2 클래스 상속
프로그램에서는 자식이 부모를 선택
public class 자식클래스 extends 부모클래스 {}
다른 언어와 달리 자바는 다중 상속을 허용하지 않는다. 즉 여러개의 부모 클래스를 상속할 수 없다. 따라서 extends 뒤에는 단 하나의 부모클래스만이 와야한다.
# 7.3 부모 생성자 호출
- 자바에서 자식 객체를 생성하면 부모 객체가 먼저 생성된 다음에 자식 객체가 생성된다.
자식클래스 변수 = new 자식클래스();
- 자식 객체만 생성되는 것처럼 보이지만, 사실은 부모 객체가 먼저 생성되고 그 다음에 자식 객체가 생성된다.
- 모든 객체는 생성자를 호출해야만 생성된다. 부모 객체도 예외는 아님. 부모 객체의 생성자는 자식 생성자의 맨 첫줄에 숨겨져 있는 super() 에 의해 호출된다.
// 자식 생성자 선언
public 자식클래스(...){
super(); // 생략가능
...
}
- super() 는 컴파일 과정에서 자동 추가. 부모의 기본 생성자를 호출한다.
- 부모 클래스에 기본 생성자가 없다면 자식 생성자 선언에서 컴파일 에러가 발생
- 부모 클래스에 기본 생성자가 없고 매개변수를 갖는 생성자만 있다면 개발자는 다음과 같이 super(매개값, ...) 코드를 직접 넣어야 한다.
- super(매개값,...) : 매개값의 타입과 개수가 일치하는 부모 생성자를 호출한다.
// 자식 생성자 선언
public 자식클래스(...) {
super(매개값, ...); // 반드시 작성해야함
}
# 7.4 메소드 재정의
# 메소드 오버라이딩(overriding)
- 상속된 메소드를 자식 클래스에서 재정의 해서 사용.
- 메소드가 오버라이딩되었다면 해당 부모 메소드는 숨겨지고, 자식 메소드가 우선적으로 사용된다.
- 메소드 오버라이딩 규칙
- 부모 메소드의 선언부(리턴타입, 메소드 이름, 매개변수) 와 동일해야 한다.
- 접근 제한을 더 강하게 오버라이딩할 수 없다(public -> private으로 변경불가)
- 새로운 예외를 throws 할 수 없다
- @Override : 컴파일 단계에서 정확히 오버라이딩이 되었는지 체크하고, 문제가 있다면 컴파일 에러를 출력한다
# 부모 메소드 호출
- 메소드를 재정의하면 부모 메소드는 숨겨지고 자식 메소드만 사용되기 때문에 비록 부모 메소드의 일부만 변경된다 하더라도 중복된 내용을 자식 메소드도 가지고 있어야 한다.
- 자식 메소드와 부모 메소드의 공동 작업 처리 기법을 이용하면 매우 쉽게 해결된다.
- super.method() : 자식 메소드 내에서 부모 메소드 호출
class Parent {
public void method(){
// 작업처리 1
}
}
class Child extends Parent {
@Override
void method(){
super.method();
// 작업처리 2
}
}
1
2
3
4
5
6
7
8
9
10
11
12
2
3
4
5
6
7
8
9
10
11
12
- super.method() 의 위치는 작업처리2 전후에 어디든지 올 수 있다. 우선 처리가 되어야 할 내용을 먼저 작성하면 된다.
- 이 방법은 부모 메소드를 재사용함으로써 자식 메소드의 중복 작업 내용을 없애는 효과를 가져온다.
# 7.5 final 클래스와 final 메소드
# final 클래스
- 클래스를 선언할 때 final 키워드를 class 앞에 붙이면 최종적인 클래스이므로 더 이상 상속할 수 없는 클래스가 된다.
- final 클래스는 부모 클래스가 될 수 없어 자식 클래스를 만들 수 없다.
- 대표적인 예 : String 클래스
public final class 클래스 [...]
# final 메소드
- 메소드를 선언할 때 final 키워드를 붙이면 이 메소드는 최종적인 메소드이므로 오버라이딩할 수 없는 메소드가 된다.
- 부모 클래스를 상속해서 자식 클래스를 선언할때, 부모 클래스에 선언된 final 메소드는 자식 클래스에서 재정의 할 수 없다.
public final 리턴타입 메소드(매개변수,...){...}
# 7.6 protected 접근 제한자
접근제한자 | 제한대상 | 제한범위 |
---|---|---|
protected | 필드, 생성자, 메소드 | 같은 패키지이거나, 자식 객체만 사용 가능 |
- protected는 같은 패키지에서는 default 처럼 접근이 가능하나, 다른 패키지에서는 자식 클래스만 접근을 허용한다. protected는 필드와 생성자 그리고 메소드 선언에 사용될 수 있다.
- 자식 클래스는 부모의 protected 필드, 생성자, 메소드에 접근 가능하다. 단 new 연산자를 사용해서 생성자를 직접 호출할 수는 없고, 자식 생성자에서 super()로 부모 생성자를 호출할 수 있다.
# 7.7 타입변환
- 클래스에서의 타입변환은 상속관계에 있는 클래스 사이에서 발생한다.
# 자동 타입 변환
- 자동적으로 타입 변환이 일어나는것.
부모타입 변수 = 자식타입객체;
// 자동 타입 변환
- 자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있다.
Cat cat = new Cat();
Animal animal = cat; // 자동 타입 변환
1
2
2
- 바로 위의 부모가 아니더라고 상속 계층에서 상위 타입이라면 자동 타입 변환이 일어날 수 있다.
- 부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능하다.
- 변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로 한정
- 그러나 자식 클래스에서 오버라이딩된 메소드가 있다면 부모 메소드 대신 오버라이딩된 메소드가 호출된다.(다형성)
class Parent {
void method1() {...}
void method2() {...}
}
class Child extends Parent{
void method2() {...} // 오버라이딩
void method3() {...}
}
class ChildExample{
public static void main(String[] args) {
Child child = new Child();
Parent parent = child;
parent.method1(); // Parent method1 호출
parent.method2(); // Child method2 호출
parent.method3(); // 호출불가능
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 강제 타입 변환
- 자식 타입은 부모 타입으로 자동 변환되지만, 반대로 부모 타입은 자식 타입으로 자동 변환되지 않는다.
- 대신 캐스팅연산자로 강제 타입 변환(casting) 을 할 수 있다.
자식타입 변수 = (자식타입) 부모타입객체;
- 부모 타입 객체를 자식 타입으로 무조건 강제 변환할 수 있는 것은 아니다. 자식 객체가 부모 타입으로 자동 변환된 후 다시 자식 타입으로 변환할때 강제 타입 변환을 사용할 수 있다.
Parent parent = new Child(); // 자동 타입 변환
Child child = (Child) parent; // 강제 타입 변환
- 자식 객체가 부모 타입으로 자동 변환 하면 부모 타입에 선언된 필드와 메소드만 사용 가능하다는 제약 사항이 따른다.
- 만약 자식 타입에 선언된 필드와 메소드를 꼭 사용해야 한다면 강제 타입 변환을 해서 다시 자식 타입으로 변환해야 한다.
# 7.8 다형성
- 사용 방법은 동일하지만 실행 결과가 다르게 나오는 성질
- 객체 사용 방법이 동일하다는 것은 동일한 메소드를 가지고 있다는 뜻
자동 타입 변환 + 메소드 오버라이딩 -> 다형성
# 필드 다형성
- 필드 타입은 동일하지만(사용 방법은 동일하지만), 대입되는 객체가 달라져서 실행 결과가 다양하게 나올 수 있는 것을 말한다.
Car myCar = new Car();
myCar.tire = new HankookTire();
myCar.run();
myCar.tire = new KumhoTire();
myCar.run();
- 어떤 타이어를 장착했는지에 따라 run() 메소드의 실행 결과는 달라지게 된다. 이것이 필드의 다형성
# 매개변수 다형성
- 메소드가 클래스 타입의 매개변수를 가지고 있을 경우, 호출할때 동일한 타입의 객체를 제공하는 것이 정석이지만, 자식 객체를 제공할 수도 있다.
public class Driver {
public void drive(Vehicle vehicle) {
vehicle.run();
}
}
Driver driver = new Driver();
Bus bus = new Bus(); // vehicle 의 자식 객체
driver.drive(bus); // Bus 객체의 run() 호출
- drive() 메소드는 매개변수 vehicle이 참조하는 객체의 run() 메소드를 호출하는데, 자식 객체가 run() 메소드를 재정의하고 있다면 재정의된 run() 메소드가 호출된다.
- 매개변수의 다형성 : 어떤 자식 객체가 제공되느냐에 따라서 drive() 의 실행 결과는 달라진다.
# 7.9 객체 타입 확인
- instanceof : 매개변수의 다형성에서 실제로 어떤 객체가 매개값으로 제공되었는지 확인하는 방법
boolean result = 객체 instanceof 타입;
- 좌항의 객체가 우항의 타입이면 true 그렇지 않으면 false;
public void method(Parent parent) {
if(parent instanceof Child) {
Child child = (Child) parent;
}
}
- Java 12 부터는 instanceof 연산의 결과가 true일 경우, 우측 타입 변수를 사용할 수 있기 때문에 강제 타입 변환이 필요 없다
public void method(Parent parent) {
if(parent instanceof Child) {
// child 변수 사용
}
}
# 7.10 추상 클래스
- 추상(abstract) : 실체 간에 공통되는 특성을 추출한 것
# 추상클래스란?
- 객체를 생성할 수 있는 클래스를 실체클래스라고 한다면, 이 클래스들의 공통적인 필드나 메소드를 추출해서 선언한 클래스를 추상클래스 라고 한다.
- 추상클래스는 실체 클래스의 부모 역할을 한다. 따라서 실체 클래스는 추상 클래스를 상속해서 공통적인 필드나 메소드를 물려받을 수 있다.
- 추상 클래스는 새로운 실체 클래스를 만들기 위한 부모 클래스로만 사용된다. 즉 extends 뒤에만 올 수 있다.
# 추상클래스 선언
- 클래스 선언에 abstract 키워드를 붙이면 추상 클래스 선언이 된다.
- 추상 클래스는 new 연산자를 이용해서 객체를 직접 만들지 못하고 상속을 통해 자식 클래스만 만들 수 있다.
public abstract class 클래스명 { // 필드 // 생성자 // 메소드 }
- 추상클래스도 필드, 메소드를 선언할 수 있다.
- 자식 객체가 생성될때 super()로 추상클래스의 생성자가 호출되기 때문에 생성자도 반드시 있어야 한다.
# 추상 메서드와 재정의
- 자식 클래스들이 가지고 있는 공통 메소드를 뽑아내어 추상 클래스로 작성할때, 메소드 선언부(리턴타입, 메소드명, 매개변수) 만 동일하고 실행 내용은 자식 클래스마다 달라야 하는 경우가 많다.
- 이런 경우를 위해 추상 클래스는 추상 메소드를 선언할 수 있다. 일반 메소드 선언과의 차이점은 abstract 키워드가 붙고, 메소드 실행 내용인 중괄호 {} 가 없다.
abstract 리턴타입 메소드명(매개변수,...);
- 추상 메소드는 자식 클래스의 공통 메소드라는 것만 정의할 뿐, 실행 내용을 가지지 않는다.
- 추상 메소드는 자식 클래스에서 반드시 재정의 (오버라이딩) 해서 실행 내용을 채워야 한다.
# 7.11 봉인된 클래스
- 기본적으로 final 클래스를 제외한 모든 클래스는 부모 클래스가 될 수 있다.
- java15 부터 무분별한 자식 클래스 생성을 방지하기 위해 봉인된(sealed)클래스가 도입되었다.
public sealed class Person permits Employee, Manager {...}
- Person 의 자식클래스는 Employee 와 Manager만 가능하고, 그 이외는 자식클래스가 될 수 없도록 Person을 봉인된 클래스로 선언
- 봉인된 클래스 Person을 상속하는 Employee 와 Manager는 final 또는 non-sealed 키워드로 다음과 같이 선언하거나, sealed 키워드를 사용해서 또 다른 봉인 클래스로 선언해야 한다.
public final class Employee extends Person {...}
public non-sealed class Manager extends Person {...}
- final : 더 이상 상속할 수 없다
- non-sealed : 봉인을 해제
- Employee는 더이상 자식 클래스를 만들 수 없지만, Manager는 자식 클래스를 만들 수 있다.