좋아요! 🎯 이번엔 상속 계층 + 인터페이스 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() 오버라이드를 안 함
✅ 호출될 메서드를 찾을 때 우선순위를 기억해야 함
자바의 우선순위 규칙:
클래스 메서드 > 인터페이스 default 메서드
즉, 상속받은 클래스의 메서드가 있으면 인터페이스 default 메서드를 무시한다!
Q는 P를 상속받았으므로 P의 print() 메서드를 이미 가지고 있음
P의 print() 메서드가 호출됨
P의 print()
✅ 인터페이스의 default 메서드가 있더라도 상위 클래스의 메서드가 우선 호출됨
✅ 인터페이스 default 메서드를 호출하고 싶으면 반드시 오버라이드하고 X.super.print() 같이 호출해야 함
✅ 클래스 상속 메서드가 인터페이스 default 메서드를 덮어쓴다고 생각하기
이렇게 오버라이드해주면 됨:
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 메서드는 오버라이드가 가능
mi.doDefault();MyInterface지만, 실제 인스턴스는 MyClass.MyClass의 doDefault() 출력MyInterface.doSomething();MyInterface의 doSomething()MyClass.doSomething();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 AB.super.show() 호출a.show();B의 show()b.show();B의 show()c.show();B의 show()💡
C의 show()가 호출될 거라 생각할 수 있지만, 실제 호출되는 건 X의 오버라이드 메서드!
A.info();A의 info()C.info();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 정적 바인딩 혼동
Base b = new Derived();b의 실제 객체 = Derived
b.print(); 호출Base.super.print() 호출Base의 default print()Base.Nested n = new Derived();n의 실제 객체 = Derived
n.print(); 호출Base.super.print() 호출Base의 default print()💡
Base.Nested참조여도 Derived 오버라이드 메서드를 호출함!
d.printNested();Nested.super.print() 호출Nested의 default print()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 메서드 호출 규칙
q.doWork();Y.super.doWork() 호출Y의 default doWork()x.doWork(); (X 참조)Y의 default doWork()y.doWork(); (Y 참조)Y의 default doWork()💡 인터페이스 참조라도 동적 바인딩되므로 Q의 오버라이드가 호출됨!
q.lambdaTest();num = 100을 람다에서 캡처 → 람다 안에서 num = 100q.localClassTest();지역 클래스 num2 = 200Q.staticTest();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 호출 가능