제이제이
article thumbnail

 

본 내용은 "윤성우의 열혈 Java 프로그래밍"를 참고로 정리한 내용입니다.

 

1. 상속을 위한 두 클래스의 관계


🔥  이전 포스팅 "상속의 기본"에서 상속을 살펴봤습니다. 

 

10. Java 객체지향 문법(10) - 상속의 기본

본 내용은 "윤성우의 열혈 Java 프로그래밍"를 참고로 정리한 내용입니다. 1. 상속 ❓ 상속이란? 🔥 상속이란 “서로 연관된 클래스에 대해 공통적인 규칙, 규약들을 정의한 것”입니다. 상속에

jay-so.tistory.com

❓그렇다면 언제 상속을 사용해야 할까?

 

이와 관련하여 ‘IS -A의 관계’라는 것을 알아봅시다.

 

상속을 적용하는 기준인 ‘IS -A 관계’

이전 시간에 상속을 배웠었습니다. 상속의 특성을 자식 클래스(하위 클래스)의 입장에서 생각해봅시다.

 

자식 클래스(하위 클래스)

🔥 1. 자식 클래스 부모 클래스의 모든 특성(인스턴스 변수, 메소드)를 지닌다.
      2. 또한 자식 클래스자신만의 추가적인 특성(자식 클래스의 변수, 메소드)를 추가하여 이용한다.

 

예를 들어서 보다 쉽게 이해해봅시다.

예시) 휴대폰(CellularPhone) vs 스마트폰(SmartPhone)

여기서 휴대폰(CellularPhone)부모 클래스, 스마트폰(SmartPhone)자식 클래스입니다.

  • 스마트폰휴대폰이 갖는 특성들을 모두 갖습니다.
  • 게다가 스마트폰은 앱 설치 및 인터넷 서칭 등과 같은 특성을 추가적으로 갖고 있습니다.

따라서 이를 클래스로 설계하면 다음과 같습니다.

class SmartPhone extends CellularPhone

 

그런데 우리는 스마트폰도 휴대폰의 한 종류라고 말합니다.

휴대폰(무선 전화기)에는 피처폰, 스마트폰 등이 있습니다.”

 

따라서 다음과 같이 이야기할 수 있습니다.

“스마트폰도 휴대폰이다”, “스마트폰은 일종의 휴대폰이다”

 

위와 같은 관계를 가리켜 ‘IS - A 관계’라고 합니다. 이는 두 클래스 간 상속의 관계를 맺기 위한 기본 조건입니다.

🔥 IS - A 관계 = “~은 일종의 ~ 이다”로 해석합니다.

 

이를 코드를 통해 이해해봅시다.

//부모 클래스(상위 클래스)
class CellularPhone{
    protected String number; //전화번호

	//생성자
    public CellularPhone(String num) {
        this.number = num;
    }
		
	//answer메소드
    public void answer(){
        System.out.println("Hi~ from " + number);
    }
}

 //자식 클래스(하위 클래스)
class SmartPhone extends CellularPhone{
    private String androidVer; //안드로이드 운영체제 버전

	//생성자
    public SmartPhone(String num, String androidVer) {
        super(num);
        this.androidVer = androidVer;
    }
		
	//playApp 메소드
    public void playApp(){
        System.out.println("App is running in " + androidVer);
    }
}

public class MobileSmartPhone {
    public static void main(String []args){
        SmartPhone phone = new SmartPhone("010-555-777","Nougat");
        phone.answer(); //전화를 받는다.
        phone.playApp(); //앱을 선택하고 실행한다.
    }

}

 

출력 결과

Hi~ from 010-555-777
App is running in Nougat

 

2. 메소드 오버라이딩


❓ 메소드 오버라이딩이란?

 🔥 부모 클래스(상위 클래스)에 정의된 메소드를 자식 클래스(하위 클래스)에서 다시 정의하는 것을 의미합니다.

 

메소드 오버로딩을 살펴보기전 다음의 내용을 한번 보고 넘어갑시다.

 

부모 클래스의 참조변수가 참조할 수 있는 대상의 범위

앞의 코드에서 스마트폰(SmartPhone)클래스가 휴대폰(CellularPhone) 클래스를 상속했었습니다.

class SmartPhone extends CellularPhone

 

따라서 다음과 같이 스마트폰의 인스턴스를 생성할 수 있었습니다.

SmartPhone phone = new SmartPhone("010-555-777"), "Nougat“);

 

그런데 부모 클래스(CellularPhone)의 참조 변수자식 클래스(SmartPhone)의 인스턴스를 참조하게 할 수도 있습니다.

CellularPhone phone = new SmartPhone("010-555-777"), "Nougat“);

 

❓ 왜 가능할까?

스마트폰은 일종의 휴대폰 중 하나이기 때문입니다.

→ 따라서 CellularPhone형 참조변수는 SmartPhone 인스턴스를 참조할 수 있습니다.

 

이를 확인하기 위해 다음의 코드를 살펴봅시다.

//부모 클래스(상위 클래스)
class CellularPhone{
    protected String number; //전화번호

	//생성자
    public CellularPhone(String num) {
        this.number = num;
    }

	//메소드 answer
    public void answer(){
        System.out.println("Hi~ from " + number);
    }
}

//자식 클래스(하위 클래스)
class SmartPhone extends CellularPhone{
    private String androidVer; //안드로이드 운영체제 버전

	//생성자
    public SmartPhone(String num, String androidVer) {
        super(num);
        this.androidVer = androidVer;
    }
		
	//메소드 playApp
    public void playApp(){
        System.out.println("App is running in " + androidVer);
    }
}

public class MobileSmartPhoneRef {
    public static void main(String[]args){
        SmartPhone ph1 = new SmartPhone("010-555-777","Nougat");
				
				//부모 클래스(상위 클래스)의 참조변수를 통해 자식 클래스(하위 클래스)의 인스턴스 참조
        CellularPhone ph2 = new SmartPhone("010-999-333","Nougat");
        ph1.answer();
        ph1.playApp();
        System.out.println();

        ph2.answer();
	//ph2.playApp();실행 불가
    }
}

 

출력 결과

Hi~ from 010-555-777
App is running in Nougat

Hi~ from 010-999-333

 

분석

위의 코드를 뜯어 봅시다.

다음과 같이 부모 클래스(상위 클래스)의 참조변수를 통해 자식 클래스(하위 클래스)의 인스턴스 참조하였습니다.

CellularPhone ph2 = new SmartPhone("010-999-333","Nougat");

 

그리고 CellularPhone 클래스에 정의된 메소드를 호출하였습니다.

ph2.answer();

 

그런데 다음과 같이 “자식 클래스(SmartPhone)에 정의된 메소드”는 호출은 불가능합니다.

ph2.playApp();

 

❓ 왜 불가능할까?

🔥 참조 변수 ph2는 CellularPhone형 참조변수입니다.
      이러한 경우 CellularPhone 클래스에 정의되어 있거나 클래스를 상속하는 자식 클래스의 멤버로 제한됩니다.

 

❓ 이유는 무엇일까?

🔥 자바는 메소드를 호출할 때 “참조 변수의 형을 참조”하여 그 메소드 호출이 옳은지 판단하기 때문입니다.

 

예시

ph2. answer(); //ph2는 CellularPhone형이므로 CellularPhone의 메소드는 호출이 가능하다.

 

배열의 관점에서 바라본 클래스의 상속과 참조변수의 가능성

위의 내용을 배열의 관점에서 살펴보겠습니다.

이를 위해 다음의 코드를 살펴봅시다.

class Cake {
	public void sweet() {...}
}

class CheeseCake extends Cake {
	public void milky() { ... }
}

 

다음과 같이 부모 클래스(상위 클래스)의 참조변수를 통해 자식 클래스(하위 클래스)의 인스턴스 참조가 가능합니다.

Cake cake = new CheeseCake();

 

이를 배열의 관점에서 살펴보면 다음과 같은 코드도 가능합니다.
(CheeseCake 배열은 일종의 Cake 배열이다)

Cake[] cakes = new CheeseCake[10];
  • 이렇듯 상속의 관계에 있는 두 클래스의 참조 관계는 배열에서까지 이어집니다.

 

메소드 오버라이딩(Methond Overriding)

앞서 설명에서 메소드 오버로딩에 대해서 설명했습니다. 다시 한번 정의에 대해 살펴봅시다.

 🔥 부모 클래스(상위 클래스)에 정의된 메소드자식 클래스(하위 클래스)에서 다시 정의하는 것을 의미합니다.

 

이를 다음의 코드를 통해 자세히 이해해봅시다.

class Cake{
    public void yummy(){
        System.out.println("Yummy Cake");
    }
}

class CheeseCake extends Cake{
	// Cake의 yummy 메소드를 오버라이딩 함
    public void yummy() { 
        System.out.println("Yummy Cheese Cake");
    }

    public void tasty(){
        super.yummy();
        System.out.println("Yumy Tasty Cake");
    }
}

public class YummyCakeOverriding {
    public static void main(String[]args){
    // 오버라이딩 한 CheeseCake의 yummy 메소드 호출됨
        Cake c1 = new CheeseCake(); 

    // 오버라이딩 한 CheeseCake의 yummy 메소드 호출됨
        CheeseCake c2 = new CheeseCake(); 
        c1.yummy();
        c2.yummy();
    }
}

 

출력 결과

Yummy Cheese Cake
Yummy Cheese Cake

 

분석

위의 CheeseCake 클래스에서 다음과 같이 yummy메소드를 오버라이딩했습니다.

class CheeseCake extends Cake{
		// Cake의 yummy 메소드를 오버라이딩 함
    public void yummy() { 
        System.out.println("Yummy Cheese Cake");
    }
}

 

🔥  메소드 오버라이딩의 조건

  • ‘메소드 오버라이딩’ 성립하려면 다음의 3가지 조건이 같아야 합니다.
 ⭕  1. 메소드의 이름
        2.메소드의 반환형
        3. 메소드의 매개변수 선언

 

따라서 Cake의 yummy 메소드를 CheeseCake의 yummy 메소드를 오버라이딩하였습니다.

  • 오버라이딩을 하면 참조변수의 형에 상관없이 오버라이딩 한 메소드(CheeseCake의 yummy 메소드)오버라이딩된 메소드(Cake의 yummy 메소드)를 대신하게 됩니다.

따라서 다음과 같이 선언된 코드는 CheeseCake의 yummy 메소드를 호출하게 됩니다.

Cake c1 = new CheeseCake();
c1.yummy();

 

❓ 그렇다면 오버라이딩 된 메소드(Cake의 yummy 메소드)를 호출하는 방법은 없을까?

이번에는 오버라이딩 된 메소드를 호출하는 방법에 대해 알아봅시다.

 

오버라이딩 된 메소드를 호출하는 방법

앞서 코드에서 Cake 클래스의 yummy 메소드는 자식 클래스 CheeseCake에 의해서 오버라이딩되었습니다.

class Cake{
	public void yummy(){...}
}

class CheeseCake extends Cake{
	public void yummy() {...} //Cake의 yummy를 오버라이딩함
}

 

따라서 다음의 코드처럼 CheeseCake 인스턴스를 생성해서 Cake클래스에 정의된 yummy 메소드를 호출하는 것은 불가능합니다.

Cake c1 = new CheeseCake(); //이 인스턴스를 대상으로는 Cake의 yummy 메소드를 호출할 수 없다.
CheeseCake c2 = new CheeseCake(); //이 인스턴스를 대상으로는 Cake의 yummy 메소드를 호출할 수 없다.

 

이럴땐 CheeseCake클래스 외부가 아닌 내부에서 super키워드를 사용해서 부모 클래스 Cake의 yummy 메소드를 호출하는 방법이 있습니다.

다음의 코드를 살펴봅시다.

class Cake3 {
    public void yummy() {
        System.out.println("Yummy Cake");
    }
}

class CheeseCake3 extends Cake3 {
    public void yummy() {
        super.yummy(); //Cake3의 yummy 메소드 호출
        System.out.println("Yummy CheeseCake");
    }
    public void tasty() {
        super.yummy(); // Cake3의 yummy 메소드 호출
        System.out.println("Yummy Tasty Cake");
    }
}

public class YummyCakeSuper {
    public static void main(String[]args){
        CheeseCake3 cake = new CheeseCake3();
        cake.yummy();
        cake.tasty();
    }
}

 

출력 결과

Yummy Cake
Yummy CheeseCake
Yummy Cake
Yummy Tasty Cake

 

정리

🔥 자식 클래스에서는 오버라이딩 된 메소드를 호출할 때 super라는 키워드를 사용하여 호출할 수 있습니다.

 

❓ 그렇다면 인스턴스 변수와 클래스 변수는 오버라이딩이 될까?

  • 부모 클래스에 선언된 변수와 동일한 이름의 변수를 자식 클래스에 선언하는 일은 코드에 혼란을 가져올 수 있어
    가급적이면 피해야 합니다.

그럼에도 인스턴스 변수가 오버라이딩 되는지 확인할 필요가 있어 다음과 같은 코드를 같이 살펴보겠습니다.

class Cake2{
    public int size; // cake2의 size

    //생성자
    public Cake2(int sz) {
        this.size = sz;
    }
    //showCakeSize 메소드
    public void showCakeSize(){
        System.out.println("Bread Ounces: " + size);
    }
}

class  CheeseCake2 extends Cake2{
    public int size; //CheeseCake2의 사이즈

    //생성자
    public CheeseCake2(int sz, int size) {
        super(sz);
        this.size = size;
    }
    //오버라이딩 된 ShowCakeSize 메소드
    public void showCakeSize(){
        System.out.println("Bread Ounces: " + super.size);
        System.out.println("Cheese Ounces: " + size);
    }
}

public class YummyCakeSize {
    public static void main(String[]args){
        CheeseCake2 ca1 = new CheeseCake2(5,7);
        Cake2 ca2 = ca1;

        // ca2는 Cake2형이므로 ca2.size는 Cake2의 멤버 size를 의미합니다.
        System.out.println("Bread Ounces: " + ca2.size);

        // ca1은 CheeseCake2형이므로 ca1.size는 CheeseCake2의 멤버 slze를 의미합니다.
        System.out.println("Cheese Ounces: " + ca1.size);
        System.out.println();

        ca1.showCakeSize();
        System.out.println();
        ca2.showCakeSize();
    }
}

 

출력 결과

Bread Ounces: 5
Cheese Ounces: 7

Bread Ounces: 5
Cheese Ounces: 7

Bread Ounces: 5
Cheese Ounces: 7

 

분석

위의 코드를 자세히 뜯어 봅시다.

부모 클래스(Cake2)와 자식 클래스(CheeseCake2)에 이름이 동일한 size변수를 선언하였습니다.

class Cake2{
    public int size; // cake2의 size
   ....
}

class CheeseCake2 extends Cake2{
    public int size; //CheeseCake2의 사이즈
	....
}

 

그러나 출력 결과를 살펴보면 변수는 오버라이딩 되지 않는 것을 확인할 수 있습니다.

CheeseCake2 c1 = new CheeseCake();
c1. size; // CheeseCake의 size에 접근

Cake2 c2 = new CheeseCake();
c2.size;//Cake의 size에 접근

 

❓ 인스턴스 변수가 오버라이딩되지 않는 이유는?

 🔥 인스턴스 변수는 ‘참조 변수의 형’에 따라서 접근하는 변수가 결정되기 때문입니다.

 

  • CheeseCake2형으로 참조변수를 선언하게 되면 CheeseCake의 size변수에 접근하게 됩니다.
  • Cake2형으로 참조변수를 선언하게 되면 Cake2의 size변수에 접근하게 됩니다.

 

❓ 그렇다면 클래스변수와 클래스 메소드는 오버라이딩되는가?

🔥 이 두가지도 ‘참조변수의 형’에 따라 접근하는 클래스 변수와 메소드가 결정되기 때문에 오버라이딩의 대상이 아닙니다.

 

3. instanceof 연산자


❓ instanceof 연산자란?

🔥 참조변수가 참조하는 인스턴스의 “클래스” 나 “상속하는 클래스”를 묻는 연산자입니다.

 

형식

if(ca instanceof Cake){ //참조변수 ca의 클래스가 Cake이거나 상속하는 클래스인가?

}

 

이와 관련해서 코드를 살펴봅시다.

class Cake4 {
}

class CheeseCake4 extends Cake4 {
}

class StrawberryCheeseCake4 extends CheeseCake4 {
}

public class YummyCakeOf {
    public static void main(String[] args){
        Cake4 cake = new StrawberryCheeseCake4();

				//참조변수 cake가 Cake4의 인스턴스이거나 상속하는 클래스인가?(true)
        if(cake instanceof Cake4){ 
            System.out.println("케익 인스턴스 or");
            System.out.println("케익 상속하는 클래스\\n");
        }

				//참조변수 cake가 CheeseCake4의 인스턴스이거나 상속하는 클래스인가?(true)
        if(cake instanceof CheeseCake4){
            System.out.println("치즈케익 인스턴스 or");
            System.out.println("치즈케익 상속하는 클래스\\n");
        }

				//참조변수 cake가 StrawberryCheeseCake4의 인스턴스이거나 상속하는 클래스인가?(true)
        if(cake instanceof StrawberryCheeseCake4){
            System.out.println("딸기 치즈케익 인스턴스 or");
            System.out.println("딸기 치즈케익 상속하는 클래스\\n");
        }
    }
}

 

출력 결과

케익 인스턴스 or
케익 상속하는 클래스

치즈케익 인스턴스 or
치즈케익 상속하는 클래스

딸기 치즈케익 인스턴스 or
딸기 치즈케익 상속하는 클래스
  • 참조변수 cake가 참조하는 인스턴스는 StrawberryCheeseCake4 인스턴스입니다.
  • 그런데 이 인스턴스의 클래스는 Cake4를 상속하는 CheeseCake4를 상속합니다. 따라서 위의 코드에 등장하는 if문의 조건은 모두 참(true)에 해당합니다.

 

📒 Reference (참고 자료)


  1. 윤성우의 열혈 자바
profile

제이제이

@아사비치즈스틱

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