[Java] 다시 정리하는 "상속" - 상속, 타입 변환과 다형성

rara_kim·2022년 12월 1일
0

Java

목록 보기
36/39

상속이란?

현실에서 상속은 부모가 자식에게 물려주는 행위를 말한다.
자식은 상속을 통해서 부모가 물려준 것을 자연스럽게 이용할 수 있다.

객체 지향 프로그래밍에서도 부모 클래스의 멤버를 자식 클래스에게 물려줄 수 있다.
프로그램에서는 부모 클래스를 상위 클래스라고 부르고, 자식 클래스를 하위 클래스 또는 파생 클래스라고 부른다.

상속은 클래스를 재사용해서 새로운 클래스를 만들기 때문에 중복되는 코드를 줄여준다.
또한 상속을 이용하면 부모 클래스의 수정으로 모든 자식 클래스들도 수정되는 효과를 가져오기 때문에 유지 보수 시간을 최소화할 수도 있다.

클래스 상속

자식 클래스를 선언할 때 어떤 부모 클래스를 상속받을 것인지 결정하고, 선택된 부모 클래스는 extends 키워드 뒤에 기술한다.

class SportsCar extends Car {
	...
}

상속의 특징

  • 여러 개의 부모 클래스를 상속할 수 없다. extends 키워드 뒤에는 하나의 부모 클래스만 올 수 있다.
  • 부모 클래스에서 Private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외된다.
    그리고 부모 클래스와 자식 클래스가 다른 패키지에 있다면 default 접근 제한을 갖는 필드와 메소드도 상속대상에서 제외된다.

부모 생성자 호출

자식 객체를 생성하면 부모 객체가 먼저 생성되고 그 다음에 자식 객체가 생성된다.
부모 생성자는 자식 생성자의 첫 줄에서 호출된다.
자식 생성자가 명시적으로 선언되지 않았다면 컴파일러가 기본 생성자를 생성해준다.

public class DmbCellPhone extends CellPhone {

    public DmbCellPhone() {
		super();
    }
}

super()는 부모의 기본 생성자를 호출한다.
개발자가 직접 자식 생성자를 선언하고 명시적으로 부모 생성자를 호출하고 싶다면 자식 생성자의 첫줄에서 부모 생성자를 호출하는 super()를 작성해야 한다.

super(매개값, ...)는 매개값의 타입과 일치하는 부모 생성자를 호출한다.
만약 매개값의 타입과 일치하는 부모 생성자가 없을 경우 컴파일 에러가 발생한다.

💡부모 클래스에 기본 생성자가 없고 매개 변수가 있는 생성자만 있다면 자식 생성자에서 반드시 부모 생성자 호출을 위해 super(매개값, ...)를 명시적으로 호출해야 한다.


메소드 재정의

부모 클래스에서 상속된 일부 메소드는 자식 클래스에서 다시 수정해서 사용할 수 있다.
이런 경우를 메소드 재정의(Overriding)이라고 한다.

메소드 재정의 방법

메소드 재정의는 자식 클래스에서 부모 클래스의 메소드를 다시 정의하는 것을 말한다.
메소드를 재정의할 때 아래과 같은 규칙에 주의해야 한다.

  • 부모의 메소드와 동일한 시그니처(리턴 타입, 메소드 이름, 매개 변수 목록)를 가져야 한다.
  • 접근 제한을 더 강하게 재정의할 수 없다.
  • 새로운 예외(Exception)를 throws 할 수 없다.

메소드가 재정의되었다면 부모 객체의 메소드는 숨겨지기 때문에, 자식 객체에서 메소드를 호출하면 재정의된 자식 메소드가 호출된다.

부모 메소드 호출

자식 클래스에서 부모 클래스의 메소드를 재정의하게 되면, 부모 클래스의 메소드가 아니라 자식 객체에서 재정의된 메소드가 호출된다.
그러나 자식 클래스 내부에서 부모 클래스의 메소드를 호출해야 하는 상황이 발생한다면 명시적으로 super 키워드를 붙여서 부모 메소드를 호출할 수 있다.

public class Airplane{    
    public void fly() {
    	System.out.println("일반비행 합니다.");	
    }
}


public class SupersonicAirplane extends Airplne {
	public static final int NORMAL = 1;
    public static final int SUPERSONIC = 2;
    
    public int flyMode = NORMAL;

	@Override
    public void fly() {
    	if(flyMode == SUPERSONIC) {
        	System.out.println("초음속 비행합니다.");
        } else {
        	super.fly();
        }
    }
}

final 클래스와 final 메소드

final 키워드는 클래스, 필드, 메소드 선언에 사용될 경우 해석이 조금씩 달리지는데, 클래스와 메소드를 선언할 때 final 키워드를 사용하면 상속과 관련이 있다는 의미이다.

상속할 수 없는 final 클래스

클래스를 선언할 때 final 키워드를 class 앞에 붙이면 이 클래스는 최종적인 클래스이므로 상속할 수 없는 클래스가 된다.
즉, final 클래스는 부모 클래스가 될 수 없어 자식 클래스를 만들 수 없다.

public final class Member {
	....
}


public class VeryImportantPerson extends Member {    -> 상속할 수 없다!!
	....
}

재정의할 수 없는 final 메소드

메소드를 선언할 때 final 키워드를 붙이면 이 메소드는 최종적인 메소드이므로 재정의할 수 없는 메소드가 된다.
즉, 부모 클래스를 상속해서 자식 클래스를 선언할 때 부모 클래스에 선언된 final 메소드는 자식 클래스에서 재정의할 수 없다.

public class MyCar {
	public int speed;

	public void speedUp() {
		speed += 1;
	}

	public final void stop() {
		System.out.println("정지");
		speed = 0;
	}
}



public class SportCar extends MyCar {
	@Override
	public void speedUp() {                -> 재정의 가능
		speed += 10;
	}

	@Override
	public void stop() {                   -> 재정의 불가능
		System.out.println("스포츠카 정지"); 
		speed = 0;
	}
}



타입 변환과 다형성

다형성은 사용 방법은 동일하지만 다양한 객체를 이용해서 다양한 실행 결과가 나오도록 하는 성질이다.

자동 타입 변환

자동 타입 변환이란 타입을 다른 타입으로 변환하는 행위를 말한다.
클래스도 타입 변환이 있는데, 클래스의 변환은 상속관계에 있는 클래스 사이에서 발생한다.
자식은 부모 타입으로 자동 타입 변환이 가능하다.

자동 타입 변환은 프로그램 실행 도중에 자동적으로 타입 변환이 일어나는 것을 말한다.
자동 타입 변환의 개념은 자식은 부모의 특징과 기능을 상속받기 때문에 부모와 동일하게 취급될 수 있다는 것이다.
예를 들어, 고양이가 동물의 특징과 기능을 상속받았다면 고양이는 동물이다.가 성립한다.

//Cat 클래스가 Animal을 상속받았으면
Cat cat = new Cat();
Animal animal = cat;

Cat 클래스로부터 cat객체를 생성하고 이것을 Animal 변수에 대입하면 자동 타입 변환이 일어난다.
cat과 animal 변수는 타입만 다를 뿐, 동일한 Cat 객체를 참조한다.

💡부모 타입으로 자동타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능하다.
비록 변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로만 한정된다.
그러나 예외가 있는데, 메소드가 자식 클래스에서 재정의되었다면 자식 클래스의 메소드가 대신 호출된다.


필드의 다형성

왜 자동 타입 변환이 필요할까? 그것은 다형성을 구현하기 위해서이다.
필드의 타입을 부모 타입으로 선언하면 다양한 자식 객체들이 저장될 수 있기 때문에 필드 사용 결과가 달라질 수 있다.  이것이 필드의 다형성이다.

예를 들어, 자동차를 구성하는 부품은 언제든지 교체할 수 있다.
부품은 고장이 날 수도 있고, 성능이 더 좋은 부품으로 교체되기도 한다. 객체 지향 프로그래밍에서도 마찬가지 이다.
프로그램은 수많은 객체들이 서로 연결되고 각자의 역할을 하게 되는데, 이 객체들은 다른 객체로 교체될 수 있어야 한다.

부모 클래스를 상속하는 자식 클래스는 부모가 가지고 있는 필드와 메소드를 가지고 있으니 사용 방법이 동일할 것이다.
자식 클래스는 부모의 메소드를 재정의해서 메소드의 실행 내용을 변경함으로써 더 우수한 실행결과가 나오게 할 수도 있다.
그리고 자식 타입을 부모 타입으로 변환할 수 있다.  이 세가지가 다형성을 구현할 수 있는 조건이 된다.

GitHub: 필드의 다형성 연습문제 풀이 코드

매개 변수의 다형성

자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만, 주로 메소드를 호출할 때 많이 발생한다.
메소드를 호출할 때에는 매개 변수의 타입과 동일한 매개값을 지정하는 것이 정석이지만, 매개값을 다양화하기 위해 매개 변수에 자식 객체를 지정할 수도 있다.

즉, 매개값으로 어떤 자식 객체가 제공되느냐에 따라 메소드의 실행결과는 다양해질 수 있다.
자식객체가 부모의 메소드를 재정의했다면 메소드 내부에서 재정의된 메소드를 호출함으로써 메소드의 실행결과는 다양해진다.

//Bus -> 자식객체
void drive(Bus bus) {
	vehicle.run();    // 자식 객체가 재정의한 run() 메소드 실행
}

GitHub: 매개 변수의 다형성 연습문제 풀이 코드

강제 타입 변환

강제 타입 변환(casting)은 부모 타입을 자식 타입으로 변환하는 것을 말한다.
모든 부모 타입을 자식 타입으로 강제 변환할 수 있는 것은 아니고, 자식 타입이 부모 타입으로 자동 타입 변환한 후 다시 자식 타입으로 변환할 때 강제 타입 변환을 사용할 수 있다.

Parent parent = new Child();    // 1. 자식 타입이 부모 타입으로 자동 타입 변환
Child child = (Child) parent;   // 2. 다시 자식 타입으로 강제 타입 변환

자식 타입이 부모 타입으로 자동 타입 변환하면, 부모에 선언된 필드와 메소드만 사용 가능하다는 제약사항이 있다. 만약 자식에 선언된 필드와 메소드를 꼭 사용해야 한다면 강제 타입 변환을 해서 다시 자식 타입으로 변환한 다음 자식의 필드와 메소드를 사용하면 된다.

GitHub: 강제 타입 변환 연습문제 풀이 코드

객체 타입 확인

강제 타입 변환은 자식 타입이 부모 타입으로 변환되어 있는 상태에서만 가능하기 때문에 처음부터 부모 타입으로 생성된 객체는 자식 타입으로 변환할 수 없다.

Parent parent = new Parent();
Child child = (Child) parent;   //강제 타입 변환 불가

어떤 객체가 어떤 클래스의 인스턴스인지 확인하기 위해 instanceof 연산자를 사용한다.
instanceof 연산자의 좌항에는 객체가 오고 우항에는 타입이 오는데, 좌항의 객체가 우항의 인스턴스이면, 즉 우항의 타입으로 객체가 생성되었다면 true를 리턴하고 그렇지 않으면 false를 리턴한다.

instanceof 연산자는 주로 매개값의 타입을 조사할 때 사용된다.
만약 확인하지 않고 강제 타입 변환을 시도한다면 ClassCastException이 발생할 수 있다.

GitHub: 객체 타입 확인 연습문제 풀이 코드

profile
느리더라도 꾸준하게

0개의 댓글