[DEV] study&learn
article thumbnail

상속

상속은 이미 잘 개발된 클래스를 재사용해서 새로운 클래스를 만들기 때문에 중복되는 코드를 줄여 준다.

상속을 이용하면 부모 클래스의 수정으로 모든 자식 클래스들도 수정되는 효과를 가져오기 때문에

유지 보수 시간을 최소화 할 수 도 있다.

 

✏️ 클래스 상속

class 자식 클래스 extends 부모 클래스 { }

자바에서 상속으 다음과 같은 특징을 가지고 있다.

여러 개의 부모 클래스를 상속할 수 없다.(다중 상속 X)

부모 클래스에서 private 접근 제한을 갖는 필드와 메소드는 상속 대상에서 제외된다.

 

✏️ 부모 생성자 호출

자식 객체를 생성하면, 부모 생성자가 먼저 생성되고 그다음에 자식 객체가 생성된다.

부모 생성자는 자식 생성자의 맨 첫 줄에서 호출된다.

예를 들어, Car의 생성자가 명시적으로 선언되지 않았다면 컴파일러는 다음과 같은 기본 생성자르 생성한다.

public Car {
	super();
}

super() 는 부모의 기본 생성자를 호출한다.

 

만약 직접 자식 생성자를 선언하고 명시적으로 부모 생성자를 호출하고 싶다면

자식클래스(매개변수) {
	super(매개값);
}

의 형태로 코드를 작성하면 된다.

super(매개값)는 매개값의 타입과 일치하는 부모 생성자를 호출한다.

 

super(매개값)가 생략되면 컴파일러에 의해 super()가 자동적으로 추가되기 때문에 부모의 기본 생성자가 존재해야한다.

 

✏️ 매소드의 재정의

부모 클래스의 모든 메소드가 자식 클래스에 맞게 설계되어 있다면 가장 이상적인 상속이지만,

어떤 메소드는 자식 클래스가 사용하기에 적합하지 않을 수도 있다.

이 경우 상속된 일부 메소드는 자식 클래스에서 다시 수정하여 사용해야하는데,

자바는 이런 경우를 위해 메소드 재정의(Overriding) 기능을 제공한다.

 

✏️ 메소드 재정의 방법

부모의 메소드와 동일한 시그니처(리턴 타입, 메소드 이름, 매개 변수 목록)를 가져야 한다.

접근 제한을 더 강하게 재정의 할 수 없다.

새로운 예외(Exception)를 throws할 수 없다.

 

메소드가 재정의되었다면 부모 객체의 메소드는 숨겨지기 때문에,

자식 객체에서 메소드를 호출하면 재정의된 자식 메소드가 호출된다.

 

// 부모 클래스
public class Calculator {
	double areaCircle(double r) {
    	return 3.14159 * r * r;
    }
}

// 자식 클래스
public class Computer extends Calculator {
	@Override
    double areaCircle(double r) {
    	;return Math.PI * r * r;
    }
}

부모 클래스의 메소드에서는 파이의 값을 3.14159 로 계산하였지만,

좀 더 정밀한 계산을 위해 자식 클래스의 메소드에서는 Math.PI 상수를 이용한다.

 

자식 클래스의 @Override 어노테이션은 생략해도 좋으나,

해당 어노테이션을 붙여주면 메소드가 정확히 재정의된 것인지 컴파일러가 확인하기 때문에 개발자의 실수를 줄여준다.

 

✏️ 부모 메소드 호출

자식 클래스에서 부모 클래스의 메소드를 재정의하게 되면,

부모 클래스의 메소드는 숨겨지고 재정의된 자식 클래스의 메소드만 사용된다.

만약 자식 클래스 내부에서 재정의된 부모 클래스의 메소드를 호출해야 하는 상황이 발생한다면

명시적으로 super 키워드를 이용해 부모 메소드를 호출할 수 있다.

super.부모메소드();

 

✏️ final 클래스와 final 메소드

final 키워드는 클래스, 필드, 메소드를 선언할 때 사용할 수 있는데,

해당 선언이 최종 상태이고 결코 수정될 수 없음을 뜻한다.

 

final 키워드는 클래스, 필드, 메소드 선언에 사용될 경우 해석이 조금씩 달라진다.

 

필드에서 final 은 초기값 설정 후 더 이상 값을 변경할 수 없다라는 의미이고,

클래스와 메소드에서 final 은 상속과 관련이 있다.

 

✏️ 상속할 수 없는 final 클래스

클래스에 final 을 붙여 선언하게 되면 이 클래스는 최종적인 클래스이므로

상속할 수 없는 클래스가 된다.

public final class String { }

(부모 클래스가 될 수 없어 자식 클래스를 만들 수 없다.)

대표적인 예로 자바 표준 API에서 제공하는 String 클래스가 있다.

 

✏️ 재정의할 수 없는 final 메소드

메소드에 final 붙여 선언하게 되면 이 메소드는 최종적인 메소드이므로

재정의할 수 없는 메소드가 된다.

public final void stop() { }

stop() 메소드는 final로 선언했기 때문에 재정의(Overriding)할 수 없다.

 

타입변환과 다형성

다형성은 사용 방법은 동일하지만

다양한 객체를 이용해서 다양한 실행결과가 나오도록 하는 성질이다.

다형성을 구현하려면 메소드 재정의와 타입 변환이 필요하다.

  • 예) 자동차는 타이어 타입으로 한국 타이어와 금호 타이어를 사용하지만
    각 타이어의 성능은 다르게 나온다.(다형성)

부모 타입의 참조 변수로 자손 타입 객체를 다루는 것.

 

Tv(부모 클래스)와 SmartTv(자식 클래스)가 있다고 예를 들어보자.

자바에서의 상속은 자식 클래스가 부모 클래스를 상속 받아서,

자식 클래스는 부모 클래스의 필드와 메스도 가지게 되고, 개별적인 자식 클래스만의 필드와 메서드도 가지게 된다.

그림으로 위와 같이 표현할 수 있는데,

그림에서 보이는 바와 같이 자식 클래스의 필드나 메서드의 개수는 부모 클래스와 같거나 크다.

(결코 적을 수 없음)

 

위의 예를 사용해,

아래의 코드와 같이 작성할 수 있다.

SmartTv s = new SmartTv(); // 일반적으로는 객체 타입과 인스턴스의 타입과 일치하게 작성하였다.
 
Tv t = new SmartTv(); // 부모 타입 참조 변수로 자식 타입 인스턴스를 참조하게도 작성할 수 있다.

 

자식 타입의 참조 변수로 부모 타입의 인스턴스를 참조할 수 없다.

따라서 코드를 아래와 같이 작성할 수 없다.

SmartTv s = new Tv();

사용될 수 있는 것 < 사용하는 것 의 형태가 되면 안된다.

사용하는 것 > 사용될 수 있는 것 의 형태가 되어야 한다.

자식 클래스로 참조를 받아 버리면, 전자의 형태가 된다.

 

즉, 기능을 할 수 있는 공간은 충분히 마련되어 있는데, 그만한 기능이 없는 것이다.

비어있는 공간을 사용하게 되면, 에러(Error)가 날 것이다.

 

✏️ 자동 타입 변환

타입 변환 : 타입을 다른 타입으로 변환하는 행위.

클래스도 타입 변환이 있는데, 상속 관계에 있는 클래스 사이에서 발생한다.

(자식은 부모 타입으로 자동 타입 변환이 가능하다.)

 

자동 타입 변환의 개념은 자식은 부모의 특징과 기능을 상속받기 때문에

부모와 동일하게 취급될 수 있다는 것이다.

// 부모타입 변수 = 자식타입;
Cat cat = new Cat(); // Animal animal = new Cat(); 도 가능
Animal animal = cat;

위 코드 처럼,

Cat 클래스로부터 Cat 객체를 생성하고 이것을 Animal 변수에 대입하면 자동 타입 변환이 일어난다.

cat과 animal 은 변수 타입만 다를 뿐 동일한 Cat 객체를 참조한다.

cat == animal // true -> 참조 변수의 == 연산은 참조 번지를 비교

 

바로 위의 부모가 아니더라도 상속 계층에서 상위 타입이라면 자동 타입 변환이 일어날 수 있다.

부모 타입으로 자동 타입 변환된 이후에는 부모 클래스에 선언된 필드와 메소드만 접근이 가능하다.

즉, 변수는 자식 객체를 참조하지만 변수로 접근 가능한 멤버는 부모 클래스 멤버로만 한정된 것이다.

 

그러나 예외가 있는데,

메소드가 자식 클래스에서 재정의(Override)되었다면 자식 클래스의 메소드가 대신 호출된다.

이것은 다형성과 관련이 있기 때문에 매우 중요!!!!

 

✏️ 필드의 다형성

왜 자식 타입을 부모 타입으로 변환해서 사용하는 걸까???

결론부터 말하자면, 다형성을 구현하기 위함이다.

필드의 타입을 부모 타입으로 선언하면 다양한 자식 객체들이 저장될 수 있기 때문에 필드 사용 결과가

달라질 수 있다.

 

자동차(클래스)의 타이어(클래스)를 생각해보자.

자동차를 처음 설계할 때 사용한 타이어는 언제든지 성능이 좋은 다른 타이어로 교체할 수 있어야 한다.

새로 교체되는 타이어는 기존 타이어와 사용 방법은 동일하지만 실행결과는 더 우수하게 나와야 할 것이다.

이것을 프로그램으로 구현하기 위해 상속과 재정의, 타입 변환을 이용하는 것이다.

 

  1. 부모 클래스를 상속하는 자식 클래스는 부모가 가지고 있는 필드와 메소드를 가지고 있으니 사용 방법이 동일하다.
  2. 자식 클래스는 부모의 메소드를 재정의해서 메소드의 실행 내용을 변경함으로써 더 우수한 실행결과가 나오게 할 수 있다.
  3. 자식 타입을 부모 타입으로 변환할 수 있다.

→ 이 세 가지가 다형성을 구현할 수 있는 기술적 조건이 된다.

 

✏️ 매개 변수의 다형성

자동 타입 변환은 필드의 값을 대입할 때에도 발생하지만,

주로 메소드를 호출할 때 많이 발생한다.

 

메소드를 호출할 때에는 매개 변수의 타입과 동일한 매개값을 지정하는 것이 정석이지만,

매개값을 다양화하기 위해 매개 변수에 자식 객체를 지정할 수도 있다.

// Driver 클래스에 drive() 메소드가 정의됨. Vehicle 타입의 매개 변수가 선언됨.
Class Driver {
	void drive(Vehicle vehicle) {
    	vehicle.run();
    }
}

// drive() 메소드를 정상적으로 호출한다면 다음과 같다.
Driver driver = new Driver();
Vehicle vehicle = new Vehicle();
driver.drive(vehicle);

 

만약 Vehicle의 자식 클래스인 Bus 객체를 drive() 메소드의 매개값으로 넘겨준다면??

Driver driver = new Driver();
Bus bus = new bus();
driver.drive(bus); // -> Vehicle vehicle = bus; 자동 타입 변환 발생.

 

우리는 여기서 매우 중요한 것을 하나 알게 되었다.

매개 변수의 타입이 클래스일 경우,

해당 클래스의 객체 뿐 아니라 자식 객체까지도 매개값으로 사용할수 있다.

 

✏️ 강제 타입 변환

클래스에서 강제 타입 변환은 부모 타입을 자식 타입으로 변환하는 것을 말한다.

// 자식타입 변수 = (자식타입) 부모타입;
Parent parent = new Child(); // 자동 타입 변환
Child child = (Child)parent;

 

자식 타입이 부모 타입으로 자동 타입 변환하면, 부모에 선언된 필드와 메소드만 사용 가능하다는 제약 사항이 따른다.

만약 자식에 선언된 필드와 메소드를 꼭 사용해야 한다면 강제 타입 변환을 해서

다시 자식 타입으로 변환한 다음 자식의 필드와 메소드를 사용하면 된다.

 

✏️ 객체 타입 확인

강제 타입 변환은 자식 타입이 부모 타입으로 변환되어 있는 상태에서만 가능하기 때문에

다음과 같이 처음부터 부모 타입으로 생성된 객체는 자식 타입으로 변환할 수 없다.

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

 

어떤 객체가 어떤 클래스의 인스턴스인지 확인하기 위해 

instanceof 연산자를 사용한다.

boolean result = 좌항(객체) instanceof 우항(타입)

 

instanceof 은 주로 매개값의 타입을 조사할 때 사용한다.

public void method(Parent parent) { // parent 에는 Parent객체, Child객체 가 들어갈 수 있다.
	if(parent instanceof Child) {
    	Child child = (Child) parent;
    }
}

자바의 정석 QnA

Q. 참조 변수의 타입은 인스턴스의 타입과 반드시 일치해야 하나요?
A. 일치하는 것이 보통이지만 일치하지 않을수도 있다!

Q. 참조 변수가 부모 타입일 때와 자식 타입을 때의 차이?
A. 참조 변수로 사용할 수 있는 멤버의 갯수가 달라진다.

Q. 자식 타입의 참조 변수로 조상 타입의 객체를 가리킬 수 있나요?
A.허용되지 않습니다!

 

profile

[DEV] study&learn

@devjuni

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!