본문 바로가기

Java

(상속) 부모 클래스 접근

훈수/저작권 관련 지적 환영합니다 - 댓글 또는 audgnssweet@naver.com

 

상속에서 부모 클래스 접근하기


아래와 같은 구조에서 접근입니다.

소스코드입니다.

class A {
    int x;
}

class B extends A{
    int x;
}

public class C extends B{
    int x;
}

위와 같이 필드의 이름이 겹치는 구조에서는 접근이 어떻게 될까요?

물론 위처럼 필드의 이름이 겹치도록 설계하는 것은 좋은 구조는 아닙니다.

메서드는 '메서드 오버 라이딩'이라고 하여 다형성의 기반이 되는 것이 존재하지만

필드의 경우에는 겹치도록 설계하는 것이 좋지 않습니다.

그럼에도 이런 경우 어떻게 접근되는지 알아보겠습니다.

 

우선 필드 접근에 대해서 이해하려면, 필드와 메서드는 접근 방식이 다르다는 것을 이해해야 합니다.

메서드는 동적 바인딩이라고 하여 상속 구조에서 runtime시에 실제 인스턴스 형태를 확인하고,

하위 클래스가 메서드 오버라이딩을 한 경우 무조건 하위 클래스의 함수가 호출되도록 하는 기능이 있습니다.

그러나 필드는 그런 기능이 없고, 접근하는 필드는 컴파일러에 의해서 결정됩니다.


먼저 this와 super에 대해서 이해해야 아래 글을 이해할 수 있습니다.

this는 객체 자기 자신을 가리키는 키워드이고

super는 자신이 상속받은 부모 클래스를 가리키는 키워드입니다.


그럼 예시로 보겠습니다.

 

1번 상황 먼저 보겠습니다.

class A {
    int x;
}

class B extends A{
    int x;
}

public class C extends B{
    int x;

    void f() {
        x = 10;       //1
        super.x = 10; //2
    }
    
    public static void main(String[] args) {
        C c = new C();
        c.f();
    }
}

위의 f-1에서 접근하는 x는 class C의 x입니다.

f-2에서 접근하는 x는 class B의 x입니다.

그렇다면 class A의 x에 접근하는 방법은 없을까요?

C에서 직접 접근하는 방법은 없습니다.

A의 x에 접근하려면 B에서 super 키워드를 통해 접근하는 방법밖에는 없습니다.

코드로 짜면 아래와 같이 나옵니다.

 

class A {
    int x;
}

class B extends A{
    int x;
    void f() {
        super.x = 10;
    }
}

public class C extends B{
    int x;

    void f() {
        x = 10;
        super.x = 10;
        super.f();
    }

    public static void main(String[] args) {
        C c = new C();
        c.f();
    }
}

B의 f()함수에서 A의 x에 접근하고 있음을 확인할 수 있습니다.

 

다음 상황입니다.

class A {
    int x;
}

class B extends A{
}

public class C extends B{
    int x;

    void f() {
        x = 10;
        super.x = 10;
    }

    public static void main(String[] args) {
        C c = new C();
        c.f();
    }
}

위와 같은 상황에서는

C에서 호출한 super.x가 class A의 x가 됩니다.

아까는 C에서 A의 x로 바로 접근할 수 있는 방법이 없다고 했잖아!

네 맞습니다. 그런데 그건 A,B,C 모두에 x가 존재한다는 가정 하에서였고

B에 x가 없다면 컴파일러는 super.x라는 문장에서

C의 상위 클래스인 B부터 시작하여 그 위 모두를 단계별로 올라가며 스캔하여 x를 찾아냅니다.

그리하여 가장 먼저 나온 x가 super.x에서 가리키는 x가 됩니다.

 

그럼 위를 조금 응용해보겠습니다

class A {
    void f() {}
}

class B extends A{
    int x;
}

public class C extends B{
    int x;

    void f() {
        x = 10;
        super.x = 10;
        super.f();
    }

    public static void main(String[] args) {
        C c = new C();
        c.f();
    }
}

C 클래스의 f()함수에서 접근하는

super.x는 B의 x이고

super.f()는 A의 f()입니다.

위 설명을 이해했다면 당연한 것입니다.

 

조금 다른 경우도 살펴보겠습니다.

class A {
    int x;
    void f() {
        System.out.println("A");
    }
}

class B extends A{
    int x;
    void f() {
        this.x = 10;  //1
        super.x = 10; //2
        super.f();    //3
    }
}

public class C extends B{
    int x;
    void f() {
        System.out.println("C");
    }

    public static void main(String[] args) {
        B b = new B();
        b.f();
    }
}

B의 f()를 주목해서 보겠습니다.

1번에서 this.x는 B의 x를 가리킵니다. 왜냐하면 main함수에서 레퍼런스 변수가 B type이기 때문입니다.

위에서도 말씀드렸듯이 접근 필드는 컴파일러단에서 결정됩니다.

컴파일러는 런타임시에 관여하지 않습니다. 일단 레퍼런스 변수가 B이기 때문에 B부터 스캔을 시작합니다. 

2번에서 super.x는 당연히 A의 x이고

3번에서 super.f()도 당연히 A의 f입니다.

 

이제부터 헷갈릴 수 있으니 집중해야 합니다.

class A {
    int x;
}

class B extends A{
    int x;
    void f() {
        this.x = 10;  //1
        super.x = 10; //2
    }
}

public class C extends B{
    int x;

    public static void main(String[] args) {
        B b = new C();
        b.f();
    }
}

위의 main함수를 집중해서 보겠습니다.

상속의 특성인 업 캐스팅을 이용하여 B type의 레퍼런스 변수에 C type의 인스턴스를 담은 상황입니다.

여기서 호출되는 f는 B의 f입니다. 왜냐하면 C에서 f()를 오버 라이딩하지 않았기 때문에

동적 바인딩이 이뤄지지 않는 상황이기 때문입니다.

 

1번에서 접근하는 this.x는 B의 x입니다. 컴파일러 입장에서 B에서 호출하는 this는

B부터 위로, 즉 B->A순으로 뒤지며 x를 찾기 때문에

1번의 this.x는 B의 x이고

2번의 super.x는 A의 x입니다.

 

이제 슬슬 필드 - 컴파일러의 특성이 이해되실 것입니다.

 

마지막으로 컴파일러의 특성을 확실하게 확인할 수 있는 예시를 보고 마무리하겠습니다.

 

class A {
    int x;
}

class B extends A{
    int x;
}

public class C extends B{
    int x;
    void f() {
        this.x = 10;
    }

    public static void main(String[] args) {
        C c = new C();
        c.f();
    }
}

위 예시에서 C함수의 f. 접근하는 this.x는 C의 x라는 것은 이제 쉽게 아실 수 있을 것입니다.

 

class A {
    int x;
}

class B extends A{
    int x;
}

public class C extends B{
    void f() {
        this.x = 10;
    }

    public static void main(String[] args) {
        C c = new C();
        c.f();
    }
}

그렇다면 이경우는 어떨까요?

컴파일러는 this가 사용된 클래스부터 시작하여 상위 클래스로 가며 스캔한다고 말씀드렸었죠.

그래서 B의 x가 됩니다.

 

class A {
    int x;
}

class B extends A{
}

public class C extends B{
    void f() {
        this.x = 10;
    }

    public static void main(String[] args) {
        C c = new C();
        c.f();
    }
}

마지막으로 이경우는 위와 같은 원리로 A의 x를 가리킵니다.

 

오늘은 상속에서 부모 클래스에 접근하는 방법에 대해서 알아보았습니다.

'Java' 카테고리의 다른 글

(상속) 업캐스팅과 다운캐스팅  (0) 2021.05.07
(상속) 생성자 호출  (0) 2021.05.07
자바 상속 기본  (0) 2021.05.07
Java static / non-static  (0) 2021.05.06
Java의 패키지와 접근 지정자  (0) 2021.05.06