java/Class.Interface.Method

[Java] 클래스 상속관계에서의 static 메서드 재정의

잘할수있을거야 2022. 2. 6. 03:11

인터페이스의 static 메서드를 공부하며 그다지 깊게 생각해보지 않았는데

상속관계에서의 static 동작에 대해 부족한 부분이 있었다. 

static 메서드의 오버라이딩에 대해 부족한 점이 문제였다.

오버라이딩이란에 대해서 다시 한번 생각해보고 자바 메모리 구조에 대해서 한번더 생각해보는 계기였다.

공부 내용을 되생각해보니 Kotlin에서 확장 프로퍼티, 확장 함수에서 접했던 hiding 되는 내용과 비슷한 면이 있는 듯하다.

 

class A {

    public static void staticMethod() {
        System.out.println("static A");
    }
}

class B extends A {

}


public class Test {

    public static void main(String[] args) {

       B.staticMethod();
    }
}

B클래스에는 메서드가 정의되어 있지 않지만 부모 클래스의 클래스 멤버를 사용할 수 있다.

부모 클래스의 메서드가 사용된다.

이러한 경우 부모 클래스의 메서드는 보이는 상태이기 때문에 hiding된 상태가 아니다.


 

class A {

    public static void staticMethod() {
        System.out.println("static A");
    }
}

class B extends A {

    public static void staticMethod(){
        System.out.println("static B");
    }
}


public class Test {

    public static void main(String[] args) {

       B.staticMethod();
       
    }
}

메서드 오버라이딩의 결과와 비슷하여 오버라이딩처럼 생각될 수 있지만 바로 밑의 내용을 보면 오버라이딩이 아닌 것을 알수 있다.

자식 클래스에서 새로운 메서드를 정의한 것으로 취급되어 마치 부모 클래스의 메서드가 hiding 되어 있는 것 같아서 이렇게 부르는 것 같다. 


참고) @Override 어노테이션을 사용하여 메서드 오버라이딩이 아님을 확인


관습을 어기고 참조변수식으로 사용해보면 오버라이딩이 아닌 것을 알 수 있다.

class A {

    public static void staticMethod() {
        System.out.println("static A");
    }
}

class B extends A {

    public static void staticMethod(){
        System.out.println("static B");
    }
}


public class Test {

    public static void main(String[] args) {

        B b= new B();

        b.staticMethod();
        ((A)b).staticMethod();

    }
}

 

메서드가 중복 선언되어 있다면 참조 변수의 타입에 해당하는 클래스의 메서드가 호출된다.


인터페이스에서 선언할 수 있는 static 메서드에 관련하여 추가 정리

interface A {
    
    //public 생략된 것
    static void staticMethod() {
        System.out.println("static A");
    }
}

class B implements A {

    public static void staticMethod(){
        System.out.println("static B");
    }
}

public class Test {

    public static void main(String[] args) {

        B.staticMethod();

    }
}

B클래스의 staticMethod에 의해 조상의 것들은 모두 hiding된다.


조상 관계에 있는 클래스와 클래스의 관계에서와 다르게 인터페이스와 클래스 관계에서는 오류가 나게 되는데

클래스에서 하나의 클래스와 여러 인터페이스를 구현할시 상속받은 것들에 static 메서드가 정의 되어있으면 어떤 것을 호출할지 모르기 때문에 위와 같이 직관적으로는 판단할 수 있어도 컴파일 에러가 나게 만들어져있음을 확인할 수 있다.

 

다음과 같은 경우가 발생할 수 있다.

 

컴파일러가 판단할 수 있는 최선을 보자

 

클래스와 여러 인터페이스들 간의 충돌 -> 클래스 우선

interface Inter1 {
    static void staticMethod() {
        System.out.println("Inter1");
    }
}

interface Inter2 {
    static void staticMethod() {
        System.out.println("Inter2");
    }
}

class C {
    static void staticMethod() {
        System.out.println("C");
    }
}

class A extends C implements Inter1, Inter2 { //클래스와 여러 인터페이스의 충돌 -> 클래스 우선
}

public class Test {

    public static void main(String[] args) {

        A.staticMethod(); //C
    }
}

 

인터페이스들간의 충돌 -> 컴파일러 몰라서 에러 발생 ( 인터페이스끼리는 다이아몬드 상속이 될 수도있어서 정하지 못하는 문제가 발생)

 

그렇기 때문에 다음과 같이 해줘야 한다.

interface Inter1 {
    static void staticMethod() {
        System.out.println("Inter1");
    }
}

interface Inter2 {
    static void staticMethod() {
        System.out.println("Inter2");
    }
}

class A implements Inter1, Inter2 { 
    //실제로는 staticMethod를 새로 선언하는 설계는 안쓴다고 봐도 무방하지만 이러한 경우
    //다음과 같이 해야한다.
    static void staticMethod(){
        Inter1.staticMethod();
    }
}

public class Test {

    public static void main(String[] args) {

        A.staticMethod(); 
    }
}

 

인터페이스의 디폴트 메서드의 경우에는 다음과 같이 된다.

 

클래스와 인터페이스 충돌 -> 클래스의 것이 우선

interface Inter1 {
    default void defaultMethod() {
        System.out.println("Inter1");
    }
}

interface Inter2 {
    default void defaultMethod() {
        System.out.println("Inter2");
    }
}

class B {
    public void defaultMethod() {
        System.out.println("B");
    }
}


class A extends B implements Inter1, Inter2 { //클래스인 B의 것으로
}

public class Test {

    public static void main(String[] args) {

        A a = new A();
        a.defaultMethod(); //B
    }
}

 

인터페이스들 간의 충돌 -> 컴파일 에러 -> 하나 선택하여 오버라이딩 필요

 

설계상 이러한 경우가 있을까 싶다. 컴파일 에러를 모면하기 위해서 필요한 방책수단으로만으로 공부하는 것 아닌가 싶다.

interface Inter1 {
    default void defaultMethod() {
        System.out.println("Inter1");
    }
}

interface Inter2 {
    default void defaultMethod() {
        System.out.println("Inter2");
    }
}


class A implements Inter1, Inter2 {
    //컴파일 에러로 무조건 둘중 하나를 선택하여 오버라이딩 해야한다.
}

public class Test {

    public static void main(String[] args) {

        A a = new A();
        a.defaultMethod();
    }
}