정보처리기사/프로그래밍 언어 활용

자바 생성자 종합 정리 및 연습문제

glorypang 2025. 10. 24. 00:57
728x90
반응형
SMALL

핵심 개념

1. 생성자 기본

  • 클래스와 이름이 동일하고 반환 타입이 없음
  • 객체 생성 시 자동으로 호출됨
  • 기본 생성자는 명시하지 않으면 자동 생성

2. 생성자 체이닝

  • this(): 같은 클래스의 다른 생성자 호출
  • super(): 부모 클래스의 생성자 호출
  • 생성자의 첫 줄에 위치해야 함

3. 상속과 생성자

  • 자식 클래스 생성 시 부모 생성자가 먼저 호출됨
  • super()가 없으면 자동으로 부모의 기본 생성자 호출

자바 생성자 연습문제

문제: 생성자 체이닝 순서 파악

질문: 출력 결과는?

class A {
    int x;
    
    A() {
        this(5);
        System.out.println("A()");
    }
    
    A(int x) {
        this.x = x;
        System.out.println("A(int): x=" + x);
    }
}

public class Test1 {
    public static void main(String[] args) {
        A a = new A();
    }
}
A(int): x=5
A()

해설: this(5)가 먼저 실행되어 A(int) 생성자가 호출된 후, A() 생성자의 본문이 실행됩니다.

문제: super() 생략 시 문제

질문: 컴파일 에러가 나는 이유는? 어떻게 고쳐야 하나?

class B {
    B(int n) {
        System.out.println("B: " + n);
    }
}

class C extends B {
    C() {
        // TODO: 여기에 코드 추가 (컴파일 되도록)
        System.out.println("C");
    }
}

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

 

에러 이유: B 클래스에 기본 생성자가 없습니다. C의 생성자에서 자동으로 super()를 호출하려 하지만, B()가 존재하지 않아 컴파일 에러가 발생합니다.

해결 방법:

class C extends B {
    C() {
        super(10);  // 또는 다른 int 값
        System.out.println("C");
    }
}

 

문제: 2단계 상속 호출 순서

질문: 출력 결과는?

class X {
    X() {
        System.out.println("X");
    }
}

class Y extends X {
    Y() {
        System.out.println("Y");
    }
}

public class Test3 {
    public static void main(String[] args) {
        Y y = new Y();
    }
}
X
Y

해설: 자식 클래스의 생성자는 항상 부모 클래스의 생성자를 먼저 호출합니다. Y의 생성자 첫 줄에 super()가 자동으로 삽입되어 X의 생성자가 먼저 실행됩니다.

문제: this()와 super() 동시 사용?

질문: 컴파일 에러가 나는가? 왜?

class D {
    D(int n) {
        System.out.println("D: " + n);
    }
}

class E extends D {
    E() {
        this(10);
        super(5);  // 이게 가능할까?
    }
    
    E(int n) {
        super(n);
    }
}

컴파일 에러 발생!

이유: this()와 super()는 모두 생성자의 첫 줄에만 올 수 있습니다. 동시에 사용할 수 없으며, 하나만 선택해야 합니다.

이 코드에서는 this(10) 다음에 super(5)가 올 수 없습니다.

문제: 3단계 상속 출력 순서

질문: 출력 결과는?

class P {
    P() {
        System.out.print("1");
    }
}

class Q extends P {
    Q() {
        System.out.print("2");
    }
}

class R extends Q {
    R() {
        System.out.print("3");
    }
}

public class Test5 {
    public static void main(String[] args) {
        R r = new R();
    }
}
123

해설: 생성자 호출 순서는 최상위 부모부터 자식 순서입니다.

  • R 생성자 호출 → Q 생성자 호출 → P 생성자 호출
  • P 생성자 실행 (출력: 1) → Q 생성자 실행 (출력: 2) → R 생성자 실행 (출력: 3)

문제: 오버라이딩과 생성자

질문: 출력 결과는? (힌트: 필드 초기화 순서)

class F {
    void show() {
        System.out.println("F");
    }
    
    F() {
        show();
    }
}

class G extends F {
    int n = 5;
    
    void show() {
        System.out.println("G: " + n);
    }
}

public class Test6 {
    public static void main(String[] args) {
        G g = new G();
    }
}
G: 0

해설:

  1. G 객체 생성 시 F의 생성자가 먼저 호출됩니다.
  2. F의 생성자에서 show() 호출 → 오버라이딩된 G의 show() 실행
  3. 하지만 이 시점에 G의 필드 n은 아직 초기화되지 않았습니다! (기본값 0)
  4. 따라서 "G: 0"이 출력됩니다.

핵심: 생성자 실행 순서는 부모→자식이지만, 메서드는 오버라이딩된 자식 메서드가 호출됩니다. 단, 자식의 필드는 아직 초기화 전입니다.

문제: 생성자에서 메서드 호출

질문: 출력 결과는?

class H {
    H() {
        init();
    }
    
    void init() {
        System.out.println("H init");
    }
}

class I extends H {
    int value = 100;
    
    void init() {
        System.out.println("I init: " + value);
    }
}

public class Test7 {
    public static void main(String[] args) {
        I i = new I();
    }
}
I init: 0

해설:

  1. I 객체 생성 → H의 생성자 먼저 호출
  2. H의 생성자에서 init() 호출 → 오버라이딩된 I의 init() 실행
  3. 이 시점에 I의 필드 value는 아직 초기화되지 않음 (기본값 0)
  4. "I init: 0" 출력

주의: 생성자에서 오버라이딩된 메서드를 호출하면 예상치 못한 결과가 나올 수 있습니다!

문제: 다형성 생성자

질문: 출력 결과는?

class J {
    J() {
        System.out.println("J");
    }
}

class K extends J {
    K() {
        System.out.println("K");
    }
}

public class Test8 {
    public static void main(String[] args) {
        J j = new K();
    }
}
J
K

해설:

  • new K()로 실제 객체는 K 타입으로 생성됩니다.
  • 참조 변수 타입이 J여도, 생성되는 객체는 K입니다.
  • 따라서 K의 생성자가 호출되고, 부모인 J의 생성자가 먼저 실행됩니다.
  • 다형성은 생성자 호출에 영향을 주지 않습니다!

문제: 생성자 체인 복잡

질문: 출력 순서는?

class L {
    L() {
        this(1);
        System.out.println("L()");
    }
    
    L(int a) {
        this(a, 2);
        System.out.println("L(int): " + a);
    }
    
    L(int a, int b) {
        System.out.println("L(int,int): " + a + "," + b);
    }
}

public class Test9 {
    public static void main(String[] args) {
        L l = new L();
    }
}

 

L(int,int): 1,2
L(int): 1
L()

해설: 생성자 체이닝 순서

  1. L() 호출 → this(1) 실행 (L()의 본문은 대기)
  2. L(int) 호출 → this(1, 2) 실행 (L(int)의 본문은 대기)
  3. L(int, int) 실행 → "L(int,int): 1,2" 출력
  4. L(int)의 본문 실행 → "L(int): 1" 출력
  5. L()의 본문 실행 → "L()" 출력

핵심: this()를 따라가서 가장 깊은 생성자가 먼저 완전히 실행된 후, 역순으로 본문이 실행됩니다.

문제: 3단계 상속 + 오버라이딩

질문: 출력 결과는?

class M {
    void test() {
        System.out.println("M");
    }
    
    M() {
        test();
    }
}

class N extends M {
    void test() {
        System.out.println("N");
    }
}

class O extends N {
    void test() {
        System.out.println("O");
    }
}

public class Test10 {
    public static void main(String[] args) {
        O o = new O();
    }
}
O

해설:

  1. O 객체 생성 → N 생성자 호출 → M 생성자 호출
  2. M의 생성자에서 test() 호출
  3. 실제 객체 타입은 O이므로, O의 test() 메서드 실행!
  4. "O" 출력

핵심:

  • 생성자는 부모부터 실행되지만
  • 메서드는 실제 객체 타입(O)의 메서드가 실행됩니다 (동적 바인딩)
  • 생성자에서 오버라이딩된 메서드를 호출하면 자식 클래스의 메서드가 실행됩니다!

문제: 기본 필드 숨김

질문: 출력 결과는?

class A {
    int i;
    public A(int i) { this.i = i; }
    int get() { return i; }
}

class B extends A {
    int i;
    public B(int i) { super(2*i); this.i = i; }
    int get() { return i; }
}

public class Main {
    public static void main(String[] args) {
        A ab = new B(7);
        System.out.println(ab.i + ", " + ab.get());
    }
}
14, 7

해설:

  • ab.i: 참조 타입이 A이므로 A의 필드 i = 14 (super(14)로 초기화됨)
  • ab.get(): 실제 객체는 B이므로 B의 get() 메서드 실행 → B의 필드 i = 7 반환
  • 핵심: 필드는 참조 타입 기준, 메서드는 실제 객체 타입 기준

문제: super.필드 접근

질문: 출력 결과는?

class A {
    int x = 10;
    int getX() { return x; }
}

class B extends A {
    int x = 20;
    int getX() { return x; }
    int getSuperX() { return super.x; }
}

public class Main {
    public static void main(String[] args) {
        A a = new B();
        B b = new B();
        System.out.println(a.x + ", " + a.getX());
        System.out.println(b.x + ", " + b.getX() + ", " + b.getSuperX());
    }
}
10, 20
20, 20, 10

해설:

  • a.x: 참조 타입 A → A의 x = 10
  • a.getX(): 실제 객체 B → B의 getX() → B의 x = 20
  • b.x: 참조 타입 B → B의 x = 20
  • b.getX(): B의 getX() → B의 x = 20
  • b.getSuperX(): super.x로 명시 → A의 x = 10

문제: 생성자에서 필드 초기화

질문: 출력 결과는?

class A {
    int num = 1;
    public A() {
        num = 10;
        System.out.print(num + " ");
    }
}

class B extends A {
    int num = 2;
    public B() {
        num = 20;
        System.out.print(num + " ");
    }
}

public class Main {
    public static void main(String[] args) {
        A a = new B();
        System.out.print(a.num);
    }
}
10 20 10

해설:

  1. B 객체 생성 → A 생성자 실행 → A.num = 10, "10 " 출력
  2. B 생성자 실행 → B.num = 20, "20 " 출력
  3. a.num: 참조 타입 A → A.num = 10 출력

문제 4: 복잡한 필드 계산

질문: 출력 결과는?

class A {
    int x;
    public A(int x) { this.x = x; }
    int calc() { return x * 2; }
}

class B extends A {
    int x;
    public B(int x) { 
        super(x + 5); 
        this.x = x; 
    }
    int calc() { return x + super.x; }
}

public class Main {
    public static void main(String[] args) {
        A a = new B(10);
        System.out.println(a.x + ", " + a.calc());
    }
}
15, 25

해설:

  • B(10) 호출 → super(15) → A.x = 15, B.x = 10
  • a.x: 참조 타입 A → A.x = 15
  • a.calc(): 실제 객체 B → B의 calc() → B.x(10) + A.x(15) = 25

문제: 3단계 상속 필드 숨김

질문: 출력 결과는?

class A {
    int n = 1;
    int get() { return n; }
}

class B extends A {
    int n = 2;
    int get() { return n + super.n; }
}

class C extends B {
    int n = 3;
    int get() { return n; }
}

public class Main {
    public static void main(String[] args) {
        A a = new C();
        B b = new C();
        C c = new C();
        System.out.println(a.n + " " + a.get());
        System.out.println(b.n + " " + b.get());
        System.out.println(c.n + " " + c.get());
    }
}
1 3
2 3
3 3

해설:

  • a.n: 참조 A → A.n = 1
  • a.get(): 객체 C → C.get() → C.n = 3
  • b.n: 참조 B → B.n = 2
  • b.get(): 객체 C → C.get() → C.n = 3
  • c.n: 참조 C → C.n = 3
  • c.get(): C.get() → C.n = 3

문제: 메서드 체이닝과 필드

질문: 출력 결과는?

class A {
    int val = 5;
    int getVal() { return val; }
    int compute() { return getVal() * 2; }
}

class B extends A {
    int val = 10;
    int getVal() { return val; }
}

public class Main {
    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.val + ", " + a.compute());
    }
}
5, 20

해설:

  • a.val: 참조 A → A.val = 5
  • a.compute(): 실제 객체 B → A의 compute() 실행
    • A.compute()에서 getVal() 호출 → 오버라이딩된 B.getVal() 실행 → 10 반환
    • 10 * 2 = 20

문제: static 필드와 인스턴스 필드

질문: 출력 결과는?

class A {
    static int x = 1;
    int y = 2;
}

class B extends A {
    static int x = 10;
    int y = 20;
}

public class Main {
    public static void main(String[] args) {
        A a = new B();
        System.out.println(a.x + ", " + a.y);
        System.out.println(A.x + ", " + B.x);
    }
}

 

1, 2
1, 10

해설:

  • a.x: static 필드는 참조 타입 기준 → A.x = 1
  • a.y: 인스턴스 필드도 참조 타입 기준 → A.y = 2
  • A.x: A의 static 필드 = 1
  • B.x: B의 static 필드 = 10 (상속이 아니라 숨김

문제: 생성자 체인 + 필드 계산

질문: 출력 결과는?

class A {
    int a;
    public A(int x) { a = x; }
    int sum() { return a; }
}

class B extends A {
    int a;
    public B(int x) { 
        super(x * 2); 
        a = x * 3; 
    }
    int sum() { return a + super.a; }
}

public class Main {
    public static void main(String[] args) {
        A obj = new B(5);
        System.out.println(obj.a + ", " + obj.sum());
    }
}
10, 25

해설:

  • B(5) → super(10) → A.a = 10, B.a = 15
  • obj.a: 참조 A → A.a = 10
  • obj.sum(): 객체 B → B.sum() → B.a(15) + A.a(10) = 25

문제: 메서드에서 this와 super

질문: 출력 결과는?

class A {
    int x = 1;
    void show() { System.out.print(x + " "); }
    void test() { show(); }
}

class B extends A {
    int x = 2;
    void show() { System.out.print(x + " "); }
}

public class Main {
    public static void main(String[] args) {
        A a = new B();
        a.test();
        System.out.print(a.x);
    }
}
2 1

해설:

  • a.test(): A의 test() 실행 → show() 호출
  • show()는 오버라이딩됨 → B.show() 실행 → B.x(2) 출력
  • a.x: 참조 A → A.x = 1

문제: 최종 보스

질문: 출력 결과는?

class A {
    int n = 1;
    public A(int n) { this.n = n; }
    int calc() { return n * 10; }
}

class B extends A {
    int n = 2;
    public B(int n) { 
        super(n + 1); 
        this.n = n; 
    }
    int calc() { return super.calc() + n; }
}

class C extends B {
    int n = 3;
    public C(int n) { 
        super(n - 1); 
        this.n = n; 
    }
    int calc() { return super.calc() + super.n + n; }
}

public class Main {
    public static void main(String[] args) {
        A a = new C(10);
        System.out.println(a.n + ", " + a.calc());
    }
}
10, 128

해설:

  • C(10) → B(9) → A(10)
    • A.n = 10, B.n = 9, C.n = 10
  • a.n: 참조 A → A.n = 10
  • a.calc(): 객체 C → C.calc() 실행
    • super.calc() → B.calc() 실행
      • super.calc() → A.calc() = A.n * 10 = 100
      • 100 + B.n(9) = 109
    • 109 + B.n(9) + C.n(10) = 128

 

728x90
반응형
LIST