※ Chapter15 클래스의 상속2: 오버라이딩
※ 본문은 윤성우의 열혈JAVA를 정리한 글입니다.
※ 문제가 될시 비공개 처리하겠습니다.
요즘 매일같이 하려고 노력하지만 안되는 부분이 있다.
'꾸준함'이다. 이게 제일 무서운 것 같다.
하지 않으면 도태되고 한다면 당연한 것이 되는 '꾸준함' 이라는 것이...
앞으로 좀 더 열심히 꾸준하게 공부해보자!
상속을 위한 두 클래스의 관계
■ 상속의 기본 조건인 'IS-A' 관계
상속이 갖는 문법적 특성을 통해서 상위 클래스와 하위 클래스를 다음과 같이 이야기할 수 있다.
"하위 클래스는 상위 클래스의 모든 특성을 지닌다."
"거기에 더하여 하위 클래스는 자신만의 추가적인 특성을 더하게 된다."
(여기서 보통 재활용이라는 말이 나오는 것인데, 재활용이라고 하면 면접에서 떨어진다. 주의하자)
ex) 모바일폰 vs 스마트폰 << 모바일폰을 스마트폰이 상속한다.
간단하게 정리하면 다음과 같다.
- IS-A 관계는 '~은 ~이다.'로 표현되는 관계이다.
ex) 노트북은 컴퓨터이다. 전기자동차는 자동차이다. 스마트폰은 모바일폰이다.
- 상속이 갖는 문법적 특성은 IS-A 관계의 표현에 적합하다.
- 상속 관계를 형성하기 위한 두 클래스는 IS-A 관계에 있어야 한다.
메소드 오버라이딩
- 상위 클래스에 정의된 메소드를 하위 클래스에서 다시 정의하는 것을 뜻한다.
■ 상위 클래스의 참조변수가 참조할 수 있는 대상의 범위
예제를 보면서 확인해보자.
다음과 같이 MobilePhone형 참조변수가 SmartPhone 인스턴스를 참조하게 할 수도 있다.
MobilePhone phone = new SmartPhone("....");
이렇듯 상위 클래스의 참조변수는 하위 클래스의 인스턴스를 참조할 수 있다.
위 예제에서는 다음과 같이 인스턴스를 생성하였다.
MobilePhone ph2 = new SmartPhone("010-999-333", "Nougat");
그리고 다음과 같이 ph2.answer();를 호출하는데 이는 당연히 가능한 일이다.
그러나 ph2.playApp(); 호출은 불가능 하다.
실제 참조하는 인스턴스가 SmartPhone 인스턴스이지만 불가능하다.
참조변수 ph2는 MobilePhone형 참조변수이다. 이러한 경우 ph2를 통해서 접근이 가능한 멤버는
MobilePhone 클래스에 정의되었거나 이 클래스가 상속하는 클래스의 멤버로 제한된다.
자바는 메소드 호출 시 '참조변수의 형을 참조'히여 그 메소드 호출이 옳은 것인지 판단한다.
예를들면 다음과 같다.
ph2.answer(); -> ph2가 모바일폰형이므로 모바일 클래스의 메소드 answer은 호출 가능!
이러한 형태의 판단은 속도가 빠르다.
"실생 시간을 늦추는 결과로 이어질 수 있습니다." -> 만일 playApp()도 호출 가능했다면 나오는 결과이다.
"참조변수의 형을 기준으로 접근 가능한 멤버를 제한하는 것은 코드를 단순하게 합니다."
단점이 많은 일부 기능을 제한함으로써 단순하고 명료한 코드의 작성을 유도하는 언어가 좋은 언어이다.
그런 측면에서 참조변수의 형을 기준으로 접근 가능한 멤버를 제한한 것은 의미가 있는 일이다.
지금까지 이야기한 내용을 정리해서 다시한번 간단하게 알아보자.
class cake {
public void sweet() {...}
}
class CheeseCake extends Cake {
public void milky() {...}
}
class StrawberryCheeseCake extends CheeseCake {
public void sour() {....}
}
이때 StrawberryCheeseCake 인스턴스는 다음과 같이 말할 수 있다.
"StrawberryCheeseCake 인스턴스는 CheeseCake 인스턴스이면서 cake 인스턴스이다."
그리고 다음과 같이 인스턴스를 참조할 수 있다.
Cake cake1 = new StrawberryCheeseCake();
CheeseCake cake2 = new StrawberryCheeseCake();
그러나 Cake형 참조변수 cake1을 통해서 호출할 수 있는 메소드는 sweet() 뿐이다.
cake2는 sweet()와 milky() 메소드들을 호출할 수 있다.
이렇듯 참조변수가 참조하는 인스턴스의 종류에 상관없이, 참조 변수의 형에 해당하는
클래스와 그 클래스가 상속하는 상위 클래스에 정의된 메소드들만 호출이 가능하다.
이때, 프로그래머들을 주의할 것이 있다.
CheeseCake ca1 = new CheeseCake();
Cake ca2 = ca1;
이 문장은 가능하다. 위에서 봐온 사실이기도 하다.
다음을 보자.
Cake ca3 = new CheeseCake();
CheeseCake ca4 = (CheeseCake)ca3;
이 문장은 어떠한가? 맞는 말이 될 것인가?
"자바는 참조변수의 형 정보를 기준으로 대입의 가능성을 판단한다."
라는 말을 자세히 살펴보자.
결국 위의 문장은 컴파일러가 가능하다. 이를 그냥 허용해버리는 것이다.
(프로그래머가 ca3가 참조하는 인스턴스가 CheeseCake 인스턴스임을 보장한다는 의미이다.)
이것은 실수가 발생한 경우지만 컴파일러가 진행되니 이같은 일이 일어나지 않도록 주의해야 한다.
■ 메소드 오버라이딩 (Method Overriding)
상위 클래스에 정의된 메소드를 하위 클래스에서 다시 정의하는 행위를 가리킨다.
오버라이딩은 '무효화 시키다.'의 뜻으로 해석이 된다.
예제를 통해 확인해보자.
CheeseCake 클래스는 Cake를 상속하면서,
Cake 에 정의된 yummy 메소드와 다음 세 가지가 같은 메소드를 정의하였다.
- 메소드의 이름, 메소드의 반환형, 메소드의 매개변수 선언
- 이 세가지가 같아야 '메소드 오버라이딩'이 성립한다.
Cake형 참조변수로 CheeseCake 인스턴스를 참조한 후 yummy 메소드를 호출하였는데,
cake의 yummy 메소드는 오버라이딩(무효화) 되었다. 따라서 이 경우에는 CheeseCake의
yummy 메소드가 대신 호출이 된 것이다.
■ 메소드 오버라이딩의 일반화
class Cake {
public void yummy() { ... }
}
class CheeseCake extends Cake {
public void yummy() { ... } // Cake의 yummy 메소드를 오버라이딩 함
}
class StrawberryCheeseCake extends CheeseCake {
public void yummy() { ... } // CheeseCake의 yummy 메소드를 오버라이딩 함
}
위와 같이 클래스를 정의한 경우 CheeseCake의 참조변수와 인스턴스의 생성문을 다음과 같이 구성할 수 있다.
cake c1 = new StrawberryCheeseCake();
CheeseCake c2 = new StrawberryCheeseCake();
StrawberryCheeseCake c3 = new StrawberryCheeseCake();
c1.yummy(); // StrawberryCheeseCake의 yummy 메소드 호출
c2.yummy(); // 동일
c3.yummy(); // 동일
이렇듯 StrawberryCheeseCake 인스턴스를 대상으로 오버라이딩 된 yummy 메소드를 호출하면,
가장 마지막으로 오버라이딩 한 yummy 메소드가 호출된다.
■ 오버라이딩 된 메소드를 호출하는 방법
다음 Cake 클래스의 yummy 메소드는 하위 클래스 CheeseCake에 의해서 오버라이딩 되었다.
class Cake {
public void yummy() { ... }
}
class CheeseCake extends Cake {
public void yummy() { ... }
}
따라서 CheeseCake 인스턴스를 생성하여 Cake 클래스에 정의된 yummy 메소드를
호출하는 것은 불가능하다.
하지만 클래스 외부가 아닌 내부에서 Cake의 yummy 메소드를 호출하는 방법이 있다.
예제를 보자.
상위 클래스의 생성자를 호출할 목적으로 키워드 super을 사용하였었지만
이렇게, 상위 클래스에 정의된, 오버라이딩 된 메소드의 호출을 목적으로도 사용한다.
■ 인스턴스 변수와 클래스 변수도 오버라이딩 대상?
상위 클래스에 선언된 변수와 동일한 이름의 변수를 하위클래스에 선언하는건 피해야 한다.
코드에 혼란을 주기 때문이다.
그렇다면 어떻게 동작하는지는 확인해보자.
위 예제에서 정의한 클래스의 핵심은 다음과 같다.
class Cake {
public int size;
}
class CheeseCake extends Cake {
public int size;
}
그런데 변수는 오버라이딩이 되지 않는다.
'참조변수 형'에 따라서 접근하는 변수가 결정된다.
CheeseCake c1 = new CheeseCake();
c1.size = ... // CheeseCake의 size에 접근
Cake c2 = new CheeseCake();
c2.size = ... // Cake의 size에 접근
클래스 변수와 클래스 메소드도 오버라이딩 대상이 아니다.
instanceof 연산자
■ instanceof 연산자의 기본
instanceof 연산자는 참조변수가 참조하는 인스턴스의 '클래스'나
참조하는 인스턴스가 '상속하는 클래스'를 묻는 연산자이다.
예를 들면 다음과 같이 문장을 구성할 수 있다.
if (ca instanceof Cake)
...
ca는 참조변수이고 Cake는 클래스 이름이다.
ca가 참조하는 인스턴스가 Cake의 인스턴스이거나
Cake를 상속하는 클래스의 인스턴스이면 true를 아니면 false를 반환한다.
예제를 보면서 확인해보자.
'JAVA' 카테고리의 다른 글
자바 기초부터 차근차근...(14) (0) | 2020.04.28 |
---|---|
자바 기초부터 차근차근...(13) (0) | 2020.04.28 |
자바 기초부터 차근차근...(12) (0) | 2020.04.26 |
자바 기초부터 차근차근...(11) (0) | 2020.04.25 |
자바 기초부터 차근차근...(10) (0) | 2020.04.24 |