함정3-다형성

류한선·2025년 6월 23일

실기연습-2

목록 보기
27/95

✅ 문제: 아래 코드를 보고 출력값을 구하시오.

반드시 코드를 끝까지 한 줄 한 줄 트레이스(디버깅) 하며 읽어야 틀리지 않아요!

class Test {
    static int num = 10;

    public static void main(String[] args) {
        int num = 5;
        num += num++ + --num; 
        System.out.println(num + " " + Test.num);
    }
}

🧠 풀이 과정 (한 줄 한 줄, 디버깅하듯이 자세히)

1️⃣ class Test { ... }

  • 클래스 정의 시작. Test.num이라는 정적 변수(num)가 10으로 초기화됨.
  • 이 값은 클래스 자체에 소속된 정적(static) 변수이므로 Test.num을 참조해야 함.

2️⃣ static int num = 10;

  • Test.num = 10 저장.
  • 이건 클래스 영역에서 존재하기 때문에 인스턴스를 생성하지 않아도 쓸 수 있음.

3️⃣ main 시작

  • int num = 5;main 메서드의 지역변수 num이 5로 초기화됨.
  • ⚠️중요: 이 num은 Test.num과 다른, 별도의 num.
    → 따라서 현재 num5, Test.num10

4️⃣ num += num++ + --num; 부분의 연산 순서

이 부분이 핵심 트릭이에요. 우선 수식을 단계별로 분해:

현재 num = 5 상태

  1. 수식: num += num++ + --num;

    연산 우선순위를 보면 num++ + --num 먼저 계산되고, 나중에 += 대입됨.

num++

  • 후위 증가 연산자이므로:

    • num++ 값 자체 = 현재 num 값(5), 이후 num을 1 증가
    • 이 부분이 끝나면 num = 6

--num

  • 전위 감소 연산자이므로:

    • num을 먼저 1 감소 → num = 5
    • --num 값 = 5

이제 num++ + --num = 5 + 5 = 10

현재 num의 값은 5 상태(--num이 한번 빼버렸으니까 6→5)

마지막 num += 10;

  • num = num + 10;
  • 현재 num(지역변수) = 5이므로 5 + 10 = 15
  • 그래서 num(지역변수) = 15

5️⃣ System.out.println(num + " " + Test.num);

  • 현재 지역변수 num = 15
  • Test.num(클래스 정적변수) = 10

출력값:

15 10

🧠 핵심 트릭 정리

변수 섀도잉: 지역 num과 Test.num은 다르다.
후위/전위 연산자 순서 꼼꼼히 계산.
한 수식 안에서 num++와 --num이 섞일 때 반드시 중간 상태를 추적.


🎯 연습 팁

이런 문제를 만났을 때:

  1. 지역변수와 클래스 변수를 확실히 구분하기.
  2. 연산자 우선순위를 반드시 써보고 계산하기.
  3. 후위 증가/전위 감소/증가 연산자는 무조건 별도로 한 단계씩 계산.
  4. 계산 중간중간 현재 num의 값 변화를 추적하기.

좋아요! 😄 이번엔 더 트리키(tricky) 하고, **스코프(scope)**와 static 초기화 블록, 그리고 메서드 호출 순서까지 포함한 문제를 내드릴게요.
이 문제를 풀어보면 정말 정보처리기사 시험에서 나오는 혼동 포인트를 잡을 수 있을 거예요.


🎯 문제 2: 아래 코드를 보고 출력값을 구하시오.

class TrickTest {
    static int a = initA();

    int b = initB();

    {
        System.out.println("인스턴스 초기화 블록: a=" + a + ", b=" + b);
    }

    static {
        System.out.println("static 초기화 블록: a=" + a);
        a += 5;
    }

    TrickTest() {
        System.out.println("생성자 호출됨: a=" + a + ", b=" + b);
        b += 10;
    }

    static int initA() {
        System.out.println("initA() 호출됨");
        return 3;
    }

    int initB() {
        System.out.println("initB() 호출됨");
        return 7;
    }

    public static void main(String[] args) {
        TrickTest t1 = new TrickTest();
        System.out.println("마지막 출력: a=" + a + ", b=" + t1.b);
    }
}

이 코드를 한 줄 한 줄 디버깅하면서 출력값을 맞춰보세요.
풀이는 아래에서 이어집니다.
우선 한 번 생각해 보신 뒤 아래를 펼쳐 주세요.


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

✏️ 클래스 초기화 단계

  1. static int a = initA();initA() 호출initA() 호출됨 출력 → a = 3

  2. static { ... }static 초기화 블록
    → 현재 a = 3 상태
    static 초기화 블록: a=3 출력
    → 이후 a += 5; → a = 3 + 5 = 8


✏️ main 메서드 시작

  1. TrickTest t1 = new TrickTest(); 호출
    → 인스턴스 생성 시작

✏️ 인스턴스 생성 단계

  1. 인스턴스 멤버 초기화 시작: int b = initB();initB() 호출됨
    initB() 호출됨 출력
    → b = 7 반환

  2. 인스턴스 초기화 블록({ ... })
    → 현재 a = 8, b = 7
    인스턴스 초기화 블록: a=8, b=7 출력

  3. 생성자 호출
    → 현재 a = 8, b = 7
    생성자 호출됨: a=8, b=7 출력
    → 이후 b += 10; → b = 17


✏️ 메인 메서드 마지막 출력

  1. System.out.println("마지막 출력: a=" + a + ", b=" + t1.b);
    → a = 8, t1.b = 17
    마지막 출력: a=8, b=17

🎯 최종 출력 순서 정리

initA() 호출됨
static 초기화 블록: a=3
initB() 호출됨
인스턴스 초기화 블록: a=8, b=7
생성자 호출됨: a=8, b=7
마지막 출력: a=8, b=17

🧠 핵심 트릭 정리

✅ static 초기화 → 인스턴스 초기화 → 생성자 호출 순서
✅ static 초기화 블록과 static 필드 초기화의 호출 순서
✅ 인스턴스 필드 초기화(initB() 호출) → 인스턴스 초기화 블록 → 생성자 호출 순서
✅ static 필드(a)와 인스턴스 필드(b) 값 변화 꼼꼼히 추적하기!


좋아요! 🎯 이번에는 인터페이스의 default 메서드 트릭 문제를 드릴게요.
이건 인터페이스 상속, 다중 인터페이스, 오버라이딩의 트릭까지 섞여 있어서 정보처리기사 시험에서 헷갈리기 좋은 부분이에요.


🎯 문제: 아래 코드를 보고 출력값을 예측하시오.

interface A {
    default void hello() {
        System.out.println("A의 hello()");
    }
}

interface B {
    default void hello() {
        System.out.println("B의 hello()");
    }
}

class C implements A, B {
    // 트릭: 여기서 hello()를 어떻게 처리해야 컴파일될까?
    @Override
    public void hello() {
        B.super.hello(); // 이 부분 트릭의 핵심!
    }
}

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

🧠 문제 포인트

A와 B가 동일한 default 메서드를 가지고 있음 → 충돌
✅ C에서 반드시 hello()를 **재정의(오버라이드)**해야 함
B.super.hello()와 같이 어떤 인터페이스의 default 메서드를 호출할지 지정할 수 있음


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


1️⃣ 인터페이스 AB 정의

interface A {
    default void hello() {
        System.out.println("A의 hello()");
    }
}

→ A의 default 메서드 hello(): "A의 hello()" 출력

interface B {
    default void hello() {
        System.out.println("B의 hello()");
    }
}

→ B의 default 메서드 hello(): "B의 hello()" 출력


2️⃣ class C implements A, B

class C implements A, B {
    @Override
    public void hello() {
        B.super.hello();
    }
}

→ 인터페이스 A와 B 모두 hello()를 가지고 있어서 C에서 오버라이드 안 하면 컴파일 오류
B.super.hello() 호출 → B 인터페이스의 hello() 호출
→ 호출 결과: "B의 hello()" 출력


3️⃣ main에서 호출

C c = new C();
c.hello(); // C에서 오버라이드한 hello() 호출

→ "B의 hello()" 출력


🎯 최종 출력

B의 hello()

🧠 트릭 정리

✅ 다중 인터페이스가 동일한 default 메서드를 가질 때 반드시 오버라이드해야 함.
InterfaceName.super.메서드() 형태로 특정 인터페이스의 default 메서드를 호출할 수 있음.
✅ 오버라이드 안 하면 컴파일 오류 발생!


0개의 댓글