프로그래밍코틀린 개발자

코틀린에서 init 블록과 super 키워드를 사용하여 객체 초기화 메커니즘을 설명하세요. 자바와 초기화 순서의 차이점은 무엇이며, 상속 계층 구조 및 생성자 호출에 대한 주의 사항은 무엇입니까?

Hintsage AI 어시스턴트로 면접 통과

답변.

코틀린의 init 블록과 super 호출은 클래스 계층 구조를 고려한 객체의 올바른 초기화를 담당합니다. 이는 자료 구조와 상속에 영향을 미치는 기본 메커니즘입니다.

질문의 역사

자바에서는 생성자를 super(...)로 명시적으로 호출하거나 기본값으로 암시적으로 호출할 수 있습니다. 반면에 코틀린에서는 각각의 주 생성자와 보조 생성자 간의 명확한 구분이 있으며, init 블록은 초기화에 사용됩니다.

문제

주요 문제는 속성이 언제 초기화되는지, init 블록이 언제 호출되는지, 특히 클래스 상속 및 주/보조 생성자 결합 시 생성자 호출 순서를 이해하는 것입니다.

해결책

  • 코틀린에서는 모든 보조 생성자가 주 생성자로 위임된 후에 슈퍼 클래스의 생성자가 호출되고, 그 뒤에 init 블록이 실행됩니다;
  • 슈퍼 생성자에 인수를 전달하려면 명시적으로 생성자에서 지정해야 합니다;
  • 클래스 본문에 선언된 속성은 init 블록이 실행되기 전에 초기화된다는 점을 기억해야 합니다.

코드 예시:

open class Base(val name: String) { init { println("Base init: $name") } } class Derived(name: String, val age: Int): Base(name) { init { println("Derived init: $name, $age") } } fun main() { Derived("Ivan", 25) } // 출력: // Base init: Ivan // Derived init: Ivan, 25

주요 특징:

  • 외부 클래스의 모든 속성이 init 블록이 실행되기 전에 초기화됩니다;
  • 먼저 슈퍼 클래스의 생성자가 실행됩니다;
  • 보조 생성자는 반드시 주 생성자를 직접 또는 체인을 통해 호출해야 합니다.

엉뚱한 질문.

상속 체계에서 init 블록은 어떤 순서로 실행됩니까?

먼저 슈퍼 클래스의 init 블록이 속성이 초기화된 후 실행되며, 그 다음에 하위 클래스의 init 블록이 자신의 속성을 초기화한 후 실행됩니다.

코드 예시:

open class A { init { println("A") } } class B: A() { init { println("B") } } fun main() { B() } // A -> B

코틀린에서 생성자 외부에서 super()를 호출할 수 있습니까?

아니요, 슈퍼 클래스의 호출은 생성자 선언의 일부로서만 이루어지며 슈퍼 생성자에 인수를 제공해야 합니다.

코틀린에서 보조 생성자가 주 생성자를 호출하지 않을 수 있습니까?

아니요, 모든 보조 생성자는 다른 보조 생성자 또는 주 생성자에게 호출을 위임해야 합니다. 주 생성자는 슈퍼 생성자를 호출하게 됩니다.

전형적인 오류 및 안티 패턴

  • 슈퍼 생성자가 호출되기 전에 계산 속성을 지정하면 오류를 발생시킵니다;
  • 초기화되지 않은 속성을 사용하려 할 때 초기화 순서에서 오류가 발생합니다;
  • 보조 생성자에서 순환 의존성 문제가 발생할 수 있습니다.

실제 사례

부정적인 케이스

개발자가 슈퍼 클래스의 init 블록에서 하위 클래스에 초기화되는 속성을 참조하려고 시도:

open class A { init { println(f()) } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s }``` **장점:** - 다형성 사용. **단점:** - 슈퍼 클래스의 init 호출 시 속성 s가 아직 초기화되지 않아 결과가 null이거나 오류가 발생합니다. ## 긍정적인 케이스 생성자에서 재정의된 메소드나 속성을 호출하지 않도록 초기화를 분리: ```kotlin open class A { init { println("init A") } open fun f() = "A" } class B : A() { private val s = "B" override fun f() = s init { println(f()) } }``` **장점:** - 초기화되지 않은 데이터에 대한 참조가 없습니다; - 초기화 순서가 보장됩니다. **단점:** - 올바른 초기화를 위해 코드량이 증가합니다.