상속

류한선·2025년 6월 23일

실기연습-2

목록 보기
28/95

좋아요! 🎯 이번엔 상속 계층 + 인터페이스 default 메서드 호출 트릭을 준비해 드릴게요.
이번 문제는 클래스가 인터페이스를 구현하고, 또 상속받는 경우default 메서드를 호출할 때 어떤 것이 우선순위인지를 묻습니다.


🎯 문제: 아래 코드를 보고 출력값을 예측해 보세요.

interface X {
    default void print() {
        System.out.println("X의 print()");
    }
}

interface Y {
    default void print() {
        System.out.println("Y의 print()");
    }
}

// 부모 클래스
class P {
    public void print() {
        System.out.println("P의 print()");
    }
}

// 자식 클래스
class Q extends P implements X, Y {
    // 트릭: 여기서 print()를 오버라이드하지 않음
}

// 메인 클래스
public class Test {
    public static void main(String[] args) {
        Q q = new Q();
        q.print();
    }
}

🧠 문제 포인트

✅ Q 클래스는 P를 상속
✅ Q 클래스는 X, Y 인터페이스를 구현 (각각 default print() 존재)
✅ 그런데 Q 클래스에서 print() 오버라이드를 안 함
✅ 호출될 메서드를 찾을 때 우선순위를 기억해야 함


🧠 정답 풀이 (디버깅 하듯 한 줄 한 줄)

1️⃣ 클래스 상속 vs 인터페이스 default 메서드 우선순위

자바의 우선순위 규칙:

클래스 메서드 > 인터페이스 default 메서드
즉, 상속받은 클래스의 메서드가 있으면 인터페이스 default 메서드를 무시한다!


2️⃣ 부모 클래스 P의 print() 존재

Q는 P를 상속받았으므로 P의 print() 메서드를 이미 가지고 있음


3️⃣ Q에서 별도로 오버라이드 안 했으므로

P의 print() 메서드가 호출됨


4️⃣ 인터페이스 X, Y의 default 메서드들은 아예 호출 안 됨!


🎯 최종 출력

P의 print()

🧠 트릭 정리

✅ 인터페이스의 default 메서드가 있더라도 상위 클래스의 메서드가 우선 호출됨
✅ 인터페이스 default 메서드를 호출하고 싶으면 반드시 오버라이드하고 X.super.print() 같이 호출해야 함
✅ 클래스 상속 메서드가 인터페이스 default 메서드를 덮어쓴다고 생각하기


🔥 추가 트릭: 그렇다면 Q에서 interface의 메서드를 호출하고 싶다면?

이렇게 오버라이드해주면 됨:

class Q extends P implements X, Y {
    @Override
    public void print() {
        X.super.print(); // 또는 Y.super.print();
    }
}

이렇게 하면 이제 출력은 "X의 print()"로 바뀜.


💡 이런 트릭은 정보처리기사 실기에서 "출력값" 묻는 문제나 "어떤 메서드를 호출하는가?" 문제로 꼭 나오는 부분이에요.


좋아요! 🎯 이번엔 인터페이스의 static 메서드 트릭 문제를 드릴게요.
이 부분은 자주 나오는 헷갈리는 부분이에요. 많은 수험생들이 인터페이스의 static 메서드를 호출하는 방법구현 클래스에서의 오버라이드 가능 여부를 혼동합니다.


🎯 문제: 아래 코드를 보고 출력값을 예측해 보세요.

interface MyInterface {
    static void doSomething() {
        System.out.println("MyInterface의 doSomething()");
    }

    default void doDefault() {
        System.out.println("MyInterface의 doDefault()");
    }
}

class MyClass implements MyInterface {
    static void doSomething() {
        System.out.println("MyClass의 doSomething()");
    }

    @Override
    public void doDefault() {
        System.out.println("MyClass의 doDefault()");
    }
}

public class Test {
    public static void main(String[] args) {
        MyInterface mi = new MyClass();

        mi.doDefault();
        MyInterface.doSomething();
        MyClass.doSomething();
    }
}

🧠 문제 포인트 정리

✅ 인터페이스의 static 메서드 호출 방식
✅ static 메서드는 상속이나 오버라이딩의 개념이 없음
✅ 인터페이스의 static 메서드는 반드시 **인터페이스 이름.메서드()**로 호출해야 함
✅ 구현 클래스에도 동일한 static 메서드가 있어도 별개임
✅ default 메서드는 오버라이드가 가능


🧠 정답 풀이

1️⃣ mi.doDefault();

  • mi의 타입은 MyInterface지만, 실제 인스턴스는 MyClass.
  • MyClass에서 doDefault()를 오버라이드했으므로 MyClass의 doDefault() 출력

2️⃣ MyInterface.doSomething();

  • 인터페이스의 static 메서드 호출.
  • 반드시 인터페이스 이름을 써야 함.
  • 출력 → MyInterface의 doSomething()

3️⃣ MyClass.doSomething();

  • 클래스 MyClass의 static 메서드 호출
  • 인터페이스의 static과 완전히 다른 메서드
  • 출력 → MyClass의 doSomething()

🎯 최종 출력

MyClass의 doDefault()
MyInterface의 doSomething()
MyClass의 doSomething()

🔥 트릭 정리

✅ 인터페이스의 static 메서드는 반드시 **인터페이스 이름.메서드()**로 호출 (객체나 구현 클래스 이름 안됨)
✅ 인터페이스의 static 메서드는 구현 클래스가 "오버라이드"하는 개념 없음. 같은 이름의 static 메서드를 클래스에 정의해도 별개의 메서드.
✅ default 메서드는 오버라이드할 수 있고, 동적 바인딩(객체의 실제 클래스 메서드 호출)됨.


좋아요! 🎯 이번엔 인터페이스 상속 트릭을 담은, 훨씬 어려운 문제를 준비해 드릴게요.
이 문제는 다중 인터페이스 상속 + default 메서드 충돌 + 업캐스팅 캐스팅 트릭까지 포함되어 있어요.

천천히 트레이스해보며 디버깅 연습하기 좋아요. 😄


🎯 문제: 아래 코드를 보고 출력값을 예측해 보세요.

interface A {
    default void show() {
        System.out.println("A의 show()");
    }
    static void info() {
        System.out.println("A의 info()");
    }
}

interface B extends A {
    default void show() {
        System.out.println("B의 show()");
    }
}

interface C extends A {
    default void show() {
        System.out.println("C의 show()");
    }
    static void info() {
        System.out.println("C의 info()");
    }
}

class X implements B, C {
    @Override
    public void show() {
        B.super.show(); // 트릭: B의 show 호출
    }
}

public class Test {
    public static void main(String[] args) {
        A a = new X();
        B b = new X();
        C c = new X();

        a.show();
        b.show();
        c.show();

        A.info();
        C.info();
    }
}

🧠 문제 포인트

✅ 인터페이스 상속 구조:

  • B extends A, C extends A
  • 둘 다 A의 default 메서드 show()를 오버라이드
    ✅ 클래스 X가 B, C 둘 다 구현
    ✅ X에서 show()를 오버라이드하고 B.super.show() 호출
    ✅ 업캐스팅된 참조(a, b, c)가 호출해도 동적 바인딩 → X의 show() 호출
    ✅ static 메서드는 인터페이스 이름을 써서 호출해야 함

🧠 정답 풀이 (한 줄 한 줄)

1️⃣ a.show();

  • a의 실제 객체는 X
  • X의 show() 오버라이드 → B.super.show() 호출 → 출력: B의 show()

2️⃣ b.show();

  • b 역시 X의 인스턴스
  • 동적 바인딩 → X의 show() → B.super.show() → 출력: B의 show()

3️⃣ c.show();

  • c 역시 X의 인스턴스
  • 동적 바인딩 → X의 show() → B.super.show() → 출력: B의 show()

💡 C의 show()가 호출될 거라 생각할 수 있지만, 실제 호출되는 건 X의 오버라이드 메서드!


4️⃣ A.info();

  • 인터페이스의 static 메서드 호출 → 출력: A의 info()

5️⃣ C.info();

  • 인터페이스 C의 static 메서드 호출 → 출력: C의 info()

🎯 최종 출력

B의 show()
B의 show()
B의 show()
A의 info()
C의 info()

🔥 트릭 정리

✅ 인터페이스를 다중 상속할 때 default 메서드 충돌 → 반드시 오버라이드 후 호출할 인터페이스 지정(B.super.show()).
✅ 업캐스팅된 참조를 호출해도 실제 오버라이드된 메서드가 호출됨(동적 바인딩 원리).
✅ 인터페이스의 static 메서드는 반드시 인터페이스명.메서드()로 호출.
✅ 인터페이스의 static 메서드는 상속받은 인터페이스에서 오버라이드되는 것이 아님.


좋아요! 🔥 이번에는 static 메서드 vs default 메서드를 한데 섞고, 중첩 인터페이스, 정적 import, 참조 타입 캐스팅까지 트릭을 총집합한 최고 난이도의 문제를 드릴게요. 😈

이 문제를 끝까지 트레이스하면서 풀면 시험장에서 대부분의 트릭 문제를 정복할 수 있을 겁니다!


🎯 문제: 출력값을 예측해 보세요.

import static java.lang.System.out;

interface Base {
    default void print() {
        out.println("Base의 default print()");
    }

    static void print() {
        out.println("Base의 static print()");
    }

    interface Nested {
        default void print() {
            out.println("Nested의 default print()");
        }

        static void print() {
            out.println("Nested의 static print()");
        }
    }
}

class Derived implements Base, Base.Nested {
    @Override
    public void print() {
        Base.super.print();    // Base의 default 호출
    }

    public void printNested() {
        Nested.super.print();  // Nested의 default 호출
    }

    public static void printStaticTest() {
        Base.print();          // Base의 static 호출
        Nested.print();        // Nested의 static 호출
    }
}

public class Test {
    public static void main(String[] args) {
        Base b = new Derived();
        Base.Nested n = new Derived();
        Derived d = new Derived();

        b.print();
        n.print();
        d.printNested();
        d.printStaticTest();
    }
}

🧠 문제 포인트 정리

default 메서드static 메서드 호출 방식
✅ 업캐스팅된 인터페이스 참조(Base, Base.Nested)
✅ Nested 인터페이스의 static 메서드 호출 트릭
super를 이용한 인터페이스 default 호출
✅ 반드시 정적 메서드는 인터페이스.메서드() 형태로 호출
✅ 동적 바인딩 vs 정적 바인딩 혼동


🧠 정답 풀이 (한 줄 한 줄 디버깅)

1️⃣ Base b = new Derived();

b의 실제 객체 = Derived

2️⃣ b.print(); 호출

  • 동적 바인딩 → Derived의 print() 호출
  • Derived의 print() 안에서 Base.super.print() 호출
  • 출력: Base의 default print()

3️⃣ Base.Nested n = new Derived();

n의 실제 객체 = Derived

4️⃣ n.print(); 호출

  • 동적 바인딩 → Derived의 print() 호출됨!
  • Derived의 print() 안에서 Base.super.print() 호출
  • 출력: Base의 default print()

💡 Base.Nested 참조여도 Derived 오버라이드 메서드를 호출함!

5️⃣ d.printNested();

  • d의 메서드 호출
  • 내부에서 Nested.super.print() 호출
  • 출력: Nested의 default print()

6️⃣ d.printStaticTest();

  • 내부에서:

    • Base.print(); → 출력: Base의 static print()
    • Nested.print(); → 출력: Nested의 static print()

🎯 최종 출력 순서

Base의 default print()
Base의 default print()
Nested의 default print()
Base의 static print()
Nested의 static print()

🔥 트릭 정리

✅ 업캐스팅된 인터페이스 참조로 호출해도 실제 오버라이드 메서드 호출됨
✅ 인터페이스의 static 메서드는 반드시 인터페이스 이름 지정
Nested.super.print() 형태로 인터페이스 default 메서드를 호출할 수 있음
✅ 인터페이스를 중첩해 정의할 수 있음
✅ default와 static은 호출 방법이 전혀 다름!


좋아요! 🔥 이번엔 정말 끝판왕급 트릭 문제를 드릴게요. 😈
지금까지 했던 트릭들에 람다식, 지역 클래스, final 지역변수 캡처 트릭, 인터페이스 다중상속 충돌, super 호출 우선순위까지 섞었습니다.

이 문제를 디버깅하듯 한 줄 한 줄 추적해 보시면 실기 트릭 문제에서 거의 모든 트릭을 대비할 수 있을 거예요.


🎯 문제: 아래 코드를 보고 최종 출력값을 예측해 보세요.

import static java.lang.System.out;

interface X {
    default void doWork() {
        out.println("X의 default doWork()");
    }
    static void info() {
        out.println("X의 static info()");
    }
}

interface Y extends X {
    default void doWork() {
        out.println("Y의 default doWork()");
    }
}

class Z {
    public void doWork() {
        out.println("Z의 doWork()");
    }
}

class Q extends Z implements Y {
    @Override
    public void doWork() {
        Y.super.doWork();    // 트릭 1: Y의 default 호출
    }

    public void lambdaTest() {
        final int num = 100;
        Runnable r = () -> {
            out.println("람다 안에서 num = " + num); // 트릭 2: final 지역변수 캡처
        };
        r.run();
    }

    public void localClassTest() {
        int num2 = 200;
        class Local {
            void print() {
                out.println("지역 클래스 num2 = " + num2); // 트릭 3: effectively final
            }
        }
        new Local().print();
    }

    public static void staticTest() {
        X.info();    // 트릭 4: 인터페이스 static 호출
    }
}

public class Test {
    public static void main(String[] args) {
        Q q = new Q();
        X x = q;
        Y y = q;

        q.doWork();
        x.doWork();
        y.doWork();

        q.lambdaTest();
        q.localClassTest();
        Q.staticTest();
    }
}

🧠 문제 포인트 정리

✅ 클래스 Z와 인터페이스 Y 모두 doWork() 있음 → 오버라이드 우선순위
✅ 인터페이스 default 메서드를 super 호출
✅ 람다식에서 final 또는 effectively final 지역변수를 캡처
✅ 지역 클래스에서 effectively final 지역변수를 캡처
✅ 인터페이스 static 메서드 호출 규칙


🧠 정답 풀이 (한 줄 한 줄 디버깅)

1️⃣ q.doWork();

  • Q의 doWork() 호출 → 내부에서 Y.super.doWork() 호출
  • 출력: Y의 default doWork()

2️⃣ x.doWork(); (X 참조)

  • 동적 바인딩 → Q의 doWork() 호출
  • 출력: Y의 default doWork()

3️⃣ y.doWork(); (Y 참조)

  • 동적 바인딩 → Q의 doWork() 호출
  • 출력: Y의 default doWork()

💡 인터페이스 참조라도 동적 바인딩되므로 Q의 오버라이드가 호출됨!


4️⃣ q.lambdaTest();

  • num = 100을 람다에서 캡처 → 람다 안에서 num = 100

5️⃣ q.localClassTest();

  • 지역 클래스 Local 내부에서 num2 참조 → 지역 클래스 num2 = 200

6️⃣ Q.staticTest();

  • 인터페이스 X의 static 호출 → X의 static info()

🎯 최종 출력

Y의 default doWork()
Y의 default doWork()
Y의 default doWork()
람다 안에서 num = 100
지역 클래스 num2 = 200
X의 static info()

🔥 트릭 정리

✅ 인터페이스 default 메서드 오버라이드 우선 → 구현 클래스의 doWork()가 호출됨
✅ 인터페이스 참조여도 동적 바인딩
✅ 람다식/지역 클래스에서 지역변수 참조 → 반드시 final 또는 effectively final
✅ 인터페이스의 static 메서드는 반드시 인터페이스 이름.메서드()로 호출
Y.super.doWork() 형태로 부모 인터페이스의 default 호출 가능


0개의 댓글