재귀 연습3

류한선·2025년 6월 22일

실기연습-2

목록 보기
26/95

📝 문제: 상속 + 전치/후치 연산자 트릭

📜 코드

class Base {
    int num = 10;

    Base() {
        num++;
        System.out.println("[Base 생성자] num = " + num); // ?
    }

    void print() {
        System.out.println("[Base.print()] num = " + num++); // ?
    }
}

class Derived extends Base {
    int num = 100;

    Derived() {
        num += 5;
        System.out.println("[Derived 생성자] num = " + num); // ?
    }

    @Override
    void print() {
        System.out.println("[Derived.print()] num = " + num++); // ?
    }
}

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

🎯 문제

  1. 프로그램의 출력값을 한 줄 한 줄 쓰기
  2. 왜 그렇게 되는지 매우 자세히 디버깅 해설하기


🎯 풀이 및 디버깅


🔍 생성자 호출 흐름 정리

1️⃣ new Derived() 호출

  1. Derived 생성자 호출 → 먼저 Base 생성자 호출

  2. Base 생성자 안에서 num++

    • Base의 num 초기값 = 10
    • num++ → 출력할 때 num 값 = 11 (후치니까 출력할 때 11), 출력 후 Base.num = 11
  3. 출력: [Base 생성자] num = 11


2️⃣ Base 생성자 끝나고 Derived 필드 초기화

  • Derived의 num = 100 초기화

3️⃣ Derived 생성자 본문

  • num += 5 → Derived.num = 105
  • 출력: [Derived 생성자] num = 105

📍 현재 상태

  • Base.num = 11
  • Derived.num = 105


🔍 b.print() 호출

  • b는 Base 타입이지만 실제 객체는 Derived → 오버라이딩된 Derived.print() 호출됨
  • 출력할 때 [Derived.print()] num = 105 (후치 연산자니까 출력 후 1 증가 → Derived.num = 106)


🎯 최종 출력

[Base 생성자] num = 11
[Derived 생성자] num = 105
[Derived.print()] num = 105


💡 트릭 포인트 정리

✅ 필드 오버라이딩이 아닌 숨김이므로 Base.num과 Derived.num은 별개
✅ 생성자 호출 순서: Base → Derived
✅ 오버라이딩된 메서드 호출은 실제 객체 타입 기준
✅ 후치 연산자는 출력 후 증가


🎯 이런 문제의 키포인트:

  • Base와 Derived에 같은 이름의 필드가 있으면 별도로 관리됨
  • 오버라이딩된 메서드 호출은 반드시 실제 객체 메서드 호출
  • num++ 출력 타이밍 반드시 생각

좋아요! 🔥 이번에는 인터페이스 + 상속 + 전치/후치 연산자 트릭을 한꺼번에 섞은 고난도 문제를 드릴게요.
이건 정말 실전 시험 트릭 문제 수준입니다. 반드시 한 줄 한 줄 디버깅해보며 풀어보셔야 합니다.


📝 문제: 인터페이스 + 상속 + 전치/후치 연산자 트릭

📜 코드

interface Printer {
    void print();
}

class Base implements Printer {
    int num = 1;

    Base() {
        num++;
        System.out.println("[Base 생성자] num = " + num++); // ?
    }

    @Override
    public void print() {
        System.out.println("[Base.print()] num = " + num++); // ?
    }
}

class Derived extends Base {
    int num = 10;

    Derived() {
        num += 10;
        System.out.println("[Derived 생성자] num = " + num--); // ?
    }

    @Override
    public void print() {
        System.out.println("[Derived.print()] num = " + num++); // ?
    }
}

public class Test {
    public static void main(String[] args) {
        Printer p = new Derived();
        p.print();
    }
}

🎯 문제

  1. 프로그램의 출력값을 한 줄 한 줄 써보세요.
  2. 왜 그런 출력이 나오는지 디버깅해 보세요.


🎯 풀이 & 디버깅

1️⃣ new Derived() 호출

생성자 호출 과정:

  • Derived() 호출 → Base() 호출

  • Base의 필드 초기화: Base.num = 1

  • Base 생성자 안:

    • num++ → 출력할 num = 2 출력 후 num = 3
    • 출력: [Base 생성자] num = 2
    • 출력 끝나고 num = 3

Base 생성자 끝난 후 Derived 필드 초기화:

  • Derived의 num = 10 초기화됨 (Base.num과 별개 필드)

  • Derived 생성자 본문:

    • num += 10 → Derived.num = 20
    • 출력할 num = 20 출력 후 num-- → Derived.num = 19
    • 출력: [Derived 생성자] num = 20

현재 상태:

  • Base.num = 3
  • Derived.num = 19

2️⃣ p.print() 호출

  • p의 참조 타입은 Printer이지만 실제 객체는 Derived
  • 오버라이딩된 Derived.print() 호출
  • 출력할 num = 19 출력 후 num++ → Derived.num = 20
  • 출력: [Derived.print()] num = 19


🎯 최종 출력

[Base 생성자] num = 2
[Derived 생성자] num = 20
[Derived.print()] num = 19


💡 트릭 포인트 정리

✅ 인터페이스(Printer) 참조이지만 오버라이딩된 메서드 호출됨
✅ 부모와 자식이 **같은 이름의 필드(num)**을 가질 때 별도로 관리
✅ 후치 연산자(num++, num--)의 출력 타이밍 반드시 주의
✅ 생성자 호출 순서: 부모(Base) → 자식(Derived)
✅ 생성자 안에서 초기화 순서:

  1. 부모 필드 초기화
  2. 부모 생성자 호출
  3. 자식 필드 초기화
  4. 자식 생성자 호출

좋아요! 🔥 이번에는 static + 오버라이딩 + 인터페이스 트릭을 한데 섞은, 정말 심화된 문제를 드릴게요.
이 문제를 꼼꼼히 디버깅하지 않으면 분명 헷갈릴 겁니다. 🎯


📝 문제: static + 오버라이딩 + 인터페이스 트릭

📜 코드

interface Speaker {
    static int num = 1;

    void speak();

    static void staticMethod() {
        System.out.println("[Speaker.staticMethod()] num = " + num++);
    }
}

class Super implements Speaker {
    static int num = 10;

    Super() {
        num++;
        System.out.println("[Super 생성자] num = " + num++);
    }

    @Override
    public void speak() {
        System.out.println("[Super.speak()] num = " + num++);
    }

    static void staticMethod() {
        System.out.println("[Super.staticMethod()] num = " + num++);
    }
}

class Sub extends Super {
    static int num = 100;

    Sub() {
        num--;
        System.out.println("[Sub 생성자] num = " + num--);
    }

    @Override
    public void speak() {
        System.out.println("[Sub.speak()] num = " + num--);
    }

    static void staticMethod() {
        System.out.println("[Sub.staticMethod()] num = " + num--);
    }
}

public class Test {
    public static void main(String[] args) {
        Speaker sp = new Sub();
        sp.speak();

        Speaker.staticMethod();
        Super.staticMethod();
        Sub.staticMethod();
    }
}

🎯 문제

  1. 위 프로그램의 출력값을 한 줄 한 줄 써보세요.
  2. 그런 출력이 나오는 이유를 매우 자세히 디버깅해 보세요.


🎯 풀이 & 디버깅


🔍 new Sub() 호출

1️⃣ 부모 Super() 호출

  • Super.num = 10

  • Super() 생성자:

    • num++ → 출력할 num = 11 → 출력: [Super 생성자] num = 11
    • num = 12

2️⃣ Sub() 생성자

  • Sub.num = 100
  • num-- → 출력할 num = 100 → 출력: [Sub 생성자] num = 100
  • num = 99

<---이부분 잘못됨 98임


현재 상태

  • Super.num = 12
  • Sub.num = 99
  • Speaker.num = 1 (인터페이스 상수; static 필드지만 final 아님!)

🔍 sp.speak()

  • sp의 실제 타입은 Sub → 오버라이딩된 Sub.speak() 호출
  • 출력할 num = 99 → 출력: [Sub.speak()] num = 99
  • num-- → Sub.num = 98

🔍 Speaker.staticMethod()

  • 인터페이스의 static 메서드 호출
  • 출력할 num = 1 → 출력: [Speaker.staticMethod()] num = 1
  • num++ → Speaker.num = 2

🔍 Super.staticMethod()

  • Super 클래스의 staticMethod 호출
  • Super.num 현재 12
  • 출력할 num = 12 → 출력: [Super.staticMethod()] num = 12
  • num++ → Super.num = 13

🔍 Sub.staticMethod()

  • Sub 클래스의 staticMethod 호출
  • Sub.num 현재 98
  • 출력할 num = 98 → 출력: [Sub.staticMethod()] num = 98
  • num-- → Sub.num = 97


🎯 최종 출력 정리

[Super 생성자] num = 11
[Sub 생성자] num = 100
[Sub.speak()] num = 99
[Speaker.staticMethod()] num = 1
[Super.staticMethod()] num = 12
[Sub.staticMethod()] num = 98


🎯 트릭 포인트 정리

static 메서드는 오버라이딩되지 않음 → 호출한 클래스나 인터페이스 기준 출력
✅ 인스턴스 메서드(speak())는 오버라이딩됨 → 동적 바인딩
✅ 인터페이스의 static int num은 인터페이스의 상수처럼 쓰임 → 따로 관리
✅ 같은 이름의 static 필드가 Super, Sub, 인터페이스 각각 따로 존재
✅ 후치연산자는 출력할 때 증가/감소하기 전 값 출력


좋아요! 🔥 이번에는 람다 + 지역클래스 + final 트릭을 섞은, 매우 까다로운 문제를 드릴게요.
이 문제를 꼼꼼히 디버깅하면서 final 캡쳐, 유효 범위, 람다 트릭을 확실히 잡아봅시다!


📝 문제: 람다 + 지역 클래스 + final 트릭

📜 코드

@FunctionalInterface
interface Printer {
    void print();
}

public class Test {
    static int staticNum = 100;

    public static void main(String[] args) {
        int localNum = 10;               // effectively final
        final int finalNum = 20;         // explicitly final
        int modifiableNum = 30;

        class LocalClass {
            int localNum = 200;
            void show() {
                System.out.println("[LocalClass.show()] localNum = " + localNum);
            }
        }

        LocalClass lc = new LocalClass();
        lc.show();

        Printer lambda = () -> {
            staticNum++;
            // localNum++;    // ❌ 컴파일 오류 (캡쳐된 지역변수는 final 이어야 함)
            // finalNum++;    // ❌ 컴파일 오류
            // modifiableNum++; // ❌ 컴파일 오류 (람다 안에서 수정 불가)
            System.out.println("[Lambda] staticNum = " + staticNum + ", localNum = " + localNum + ", finalNum = " + finalNum);
        };
        lambda.print();

        // 바깥쪽 modifiableNum은 여기서만 수정 가능
        modifiableNum++;
        System.out.println("[Main End] modifiableNum = " + modifiableNum);
    }
}

🎯 문제

  1. 프로그램의 출력값을 한 줄 한 줄 써보세요.
  2. 위 주석 부분(localNum++, finalNum++, modifiableNum++ 안에서 수정)에서 왜 컴파일 오류가 나는지 설명해 주세요.
  3. 이 문제를 디버깅하면서 람다 캡쳐링과 final 트릭을 정리해 주세요.


🎯 풀이 및 디버깅


🔍 지역 클래스 LocalClass 부분

  1. localNum = 200(이건 지역 클래스 안의 필드) → show() 호출 → [LocalClass.show()] localNum = 200

🔍 람다 부분

  1. 람다는 바깥 정적 필드(staticNum) 자유롭게 수정 가능 → 호출 전에 staticNum = 100이었음 → 람다 안에서 staticNum++staticNum = 101
  2. 람다는 지역 변수를 캡쳐할 수 있지만 반드시 final이거나 effectively finallocalNum은 main 내부에서 수정 안 했으므로 effectively final → 출력됨
  3. finalNum은 명시적 final → 출력됨
  4. modifiableNum은 수정된 적 있으므로 람다 안에서 캡쳐 불가 → 주석 부분 에러
  5. 출력: [Lambda] staticNum = 101, localNum = 10, finalNum = 20

🔍 마지막 출력

  1. 람다 호출 후 modifiableNum++ → 30에서 31로 증가
  2. 출력: [Main End] modifiableNum = 31


🎯 정리된 출력값

[LocalClass.show()] localNum = 200
[Lambda] staticNum = 101, localNum = 10, finalNum = 20
[Main End] modifiableNum = 31


🎯 트릭 포인트 정리

람다는 외부 지역 변수를 캡쳐할 때 반드시 final 또는 effectively final 이어야 함 → 그렇지 않으면 컴파일 오류
✅ 람다 안에서 지역변수를 수정 불가 → final 트릭
✅ static 필드는 람다 안에서 자유롭게 수정 가능
✅ 지역 클래스 내부의 필드는 외부 지역변수와 별개 (여기서 localNum 필드와 지역변수 localNum 혼동 주의!)
✅ effectively final: 한 번 초기화 후 값이 변하지 않는 경우 final처럼 취급


0개의 댓글