티스토리 뷰

JAVA/JAVA

static은 상속이 될까?

최진영 2021. 3. 29. 02:02

 이 고민은 부모클래스에 static이 붙어있었을 때 왜 자식 클래스에서 이를 왜 Overriding 못 하는가?에 대한 궁금증에서부터 시작되었다.

 

인스턴스 메소드의 상속

 사전지식으로 보아야할 Dynamic Method Dispatch 예를 보자.

package Week6;

public class Exam4{
    public static void main(String[] args) {
        Parent parent1 = new Parent();
        Parent parent2 = new Child();
        Child child = new Child();

        parent1.getTest();
        parent2.getTest();
        child.getTest();
    }
}
class Parent {
    void getTest() {
        System.out.println("parent");
    }
}
class Child extends Parent {
    @Override
    void getTest() {
        System.out.println("child");
    }
}

//    [출력]
//    parent
//    child
//    child

 parent1은 Parent 참조변수에 Parent 객체를 받아 parent를 출력하였고 child는 Child 참조변수에 Child 객체를 받았고 Child에서 getTest()가 Override되어 child를 출력하였다.

 

 그럼 parent2는 Parent 참조변수에 Child 객체를 받았으니까 Parent클래스에 있는 getTest()를 받겠지? 는 틀린 말이다. 물론 컴파일과정에서는 아래와 같이 Parent를 실제로 받기도 했다.

  //parent2.getTest()의 ByteCode
  L4
    LINENUMBER 10 L4
    ALOAD 2
    INVOKEVIRTUAL Week6/Parent.getTest ()V

 하지만 Child 클래스의 getTest()를 받아온 이유는 RunTime과정에서 Dynamic Method Dispatch가 발생했기 때문이다. 컴파일 타임에서는 참조 타입을 확인했지만, 런타임에서 JVM이 객체의 타입을 확인고 그 객체에 해당된 메소드를 실행시켜준 것이다.

 

 

Static ?

 다음으로 인스턴스 메소드가 아닌 static 메소드를 알아보기 위해서는 static이 뭔지, static이 언제 메모리에 로드되는지, 인스턴스와 차이점이 뭔지를 알아야 한다.

  1. (변화움직임이 없이) 고정된 2. 정지 상태의

 자바에서는 static을 메모리에 고정적으로 할당하여 프로그램이 끝날 때까지 유지하여 사용한다. 인스턴스와는 조금 다르다. 인스턴스는 내가 사용하려고 할 때 인스턴스화를 통해서 직접 할당을 해줘야 사용이 가능하기 때문이다.

 따라서, JVM의 메모리에는 static이 저장되는 영역(Method Area)과 인스턴스가 저장되는 영역(Heap)이 다르고 그 둘이 저장되는 시간 또한 다르다. Method Area에 저장되는 값은 컴파일 타임에서 저장하며, Heap Area에 저장되는 값은 런타임에 저장된다.

  • Static Binding : Compile Time에 메모리에 올라감
  • Dynamic Binding : Run Time에 메모리에 올라감

 우리가 사용하는 Overriding은 결국 Instance 메소드이다. 하지만 이미 부모클래스에서 static으로 만들어진 메소드는 인스턴스되기 전에 Method Area에 저장되는 부모클래스의 고유한 값이다. 물론 자식클래스에서 그 메소드로 접근은 가능하겠지만(접근 지시자가 허용한다면) 자식 클래스에서 재정의하지 못하는 자식 클래스와는 연관성이 없는 값이 되는 것이다.

 

Hiding

엥? 부모 클래스 static 메소드를 자식 클래스에서 똑같은 이름의 static 메소드로 정의 가능하던데 Overriding되는거 아닌가요??

 물론 동일한 메소드 이름으로 생성되긴 하지만 그게 Overriding되어서 재정의되는 것이 아니다. Overriding인지 아닌지는 일단 다음과 같이 IDE에서 @Override 어노테이션을 보면 확인할 수 있다.

같은 메소드명이라서 @Override 어노테이션이 사용될 줄 알았지만 컴파일 에러메시지가 뜬다.

 

 그럼 다음과 같이 static 메소드가 정의되는건 뭘까?

class Parent {
    static void getTest() {
        System.out.println("parent");
    }
}
class Child extends Parent {
    static void getTest() {
        System.out.println("child");
    }
}

 엄밀히 말하면 Overriding이 아니라 Hiding이라한다.

부모 클래스에 있는 static 메소드를 자식 클래스에서 다시 정의하면 부모 클래스에 있는 메모드가 가려지게 된다(Hiding). 가려진 상태는 존재가 지워지거나 삭제된 것이 아니라 해당 메소드의 호출 환경(부모의 참조, 자식의 참조)에 따라서 2개의 메소드를 모두 사용할 수 있다는 의미이다. 이와는 달리 오버라이딩은 부모의 참조나 자식의 참조에 상관 없이 오버라이딩한 메소드가 호출된다.

출처: https://micropilot.tistory.com/3050

 

 위와 같이 동일한 이름으로 static 메소드를 정의한 경우 우리가 사용하는 메소드는 Override 상속되어 재정의해서 사용하는 것이 아닌 자식 클래스가 온전히 가질 수 있는 static 메소드를 정의한 것과 같다. 존재가 지워지거나 삭제된 것이 아닌 메소드의 호출(부모 참조, 자식 참조)에 따라서 다르게 사용할 수 있기 때문에 Hiding Method라고 불린다.

package Week6;

public class Exam4{
    public static void main(String[] args) {
        Parent parent1 = new Parent();
        Parent parent2 = new Child();
        Child child = new Child();

        parent1.getTest();
        parent2.getTest();
        child.getTest();
    }
}
//    [출력]
//    parent
//    parent
//    child

 처음에 출력해봤던 인스턴스 메소드의 상속이랑은 결과가 다름을 알 수 있다. RunTime과정에서 객체 타입을 확인하고 객체에 해당된 메소드를 지정해줬던 것과는 다르게 Parent 참조변수가 가르키는 getTest()가 호출되었다. 따라서 static 메소드는 RunTime은 상관없이 항상 Compile Time 의존하여 사용한다는 것을 알 수 있다.

 따라서 부모 클래스의 static 메소드는 자식 클래스에서 상속받을 수 없다.

 

자식 클래스에서 부모 static 메소드를 그냥 호출할 수 있던데?

 이 상황을 뜻하는 예를 한번 보자.

package Week6;

public class Exam4{
    public static void main(String[] args) {
        Child child = new Child();
        child.childTest();
        child.childTest2();
    }
}
class Parent {
    static void getTest() {
        System.out.println("parent");
    }
}
class Child extends Parent {
    void childTest() {
        getTest();
    }
    static void childTest2() {
        getTest();
    }
}
//    [출력]
//    parent
//    parent

 자식 클래스가 상속받았을 때 "상속"으로써 부모 클래스의 메소드를 모두 사용할 수 있는 것처럼 보이긴 하지만 내부가 조금 다르다.

 

class Child extends Parent {
    public Child() {
        super();
    }
    void childTest() {
        super.getTest();
        Parent.getTest();
    }
    static void childTest2() {
      //super.getTest();
        Parent.getTest();
    }
}

 위 코드에 생략된 코드를 전부 넣어서 만든 코드이다. 우리는 상속된 메소드를 사용할 때 super 키워드를 사용해서 명시적으로 이를 호출하곤 한다. 따라서 상속으로써 메소드를 사용한다면 super.getTest()를 사용할 수 있겠지만 static 메소드인 childTest2()에서는 이를 사용하면 컴파일 에러가 나며, Parent 클래스로부터 직접 getTest()를 받아와야한다는 것을 알 수 있다.

 이로부터 알 수 있는 사실은 자식 클래스의 static 메소드는 상속에 제한을 받는다는 것이다. 상속에 사용되는 대부분의 메소드와 변수들은 super가 생략된 채로 사용되며 실제 상속으로 사용될 때는 super를 붙여도 컴파일 에러가 발생하지 않는다. 즉, static 메소드는 부모 클래스로부터 상속으로써 사용하는 것이 아닌 직접 호출 받아서 사용하는 메소드라고 할 수 있다. 굳이 상속받지 않아도 사용할 수 있는 다음 코드처럼 말이다.

class NoChild {
    void noChildTest() {
        Parent.getTest();
    }
    static void noChildTest2() {
        Parent.getTest();
    }
}

 

 

결론

 static 메소드는 Compile Time에 메모리에 올라가기 때문에 클래스에 종속적이기 때문에 상속되지 않는다. 객체가 생성되기 전부터 이미 메모리에 할당되어있기 때문이다.

 자식 static 메소드에서 static 메소드를 호출할 수 있더라도 이는 상속으로써 호출하는 것이 아닌 클래스를 통해서 호출하는것과 같다.

'JAVA > JAVA' 카테고리의 다른 글

For Each는 For보다 얼마나 향상되었을까?  (0) 2021.03.28
댓글
최근에 올라온 글
Total
Today
Yesterday