프로그래밍백엔드 개발자

코틀린에서 제너릭은 어떻게 구현되었나요? 어떠한 제약이 있으며, 불변성, 공변성 및 반공변성은 어떻게 작용하며, 이는 자바의 제너릭과 어떻게 다릅니까? 사용 예를 제시하세요.

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

답변

제너릭은 코틀린에서 유용하고 타입 안전한 데이터 구조 및 함수를 생성할 수 있게 해줍니다. 주요 특징: 코틀린은 컴파일 수준에서 제너릭 타입 시스템을 구현하며, 자바와 유사하지만 더 엄격한 타입 시스템과 확장된 분산 문법을 갖추고 있습니다(공변성과 반공변성).

제너릭의 제약:

  • 타입 파라미터는 기본적으로 불변입니다.
  • 타입 파라미터의 인스턴스를 생성할 수 없습니다(T()는 금지됨).
  • 실행 중에 제너릭 타입에 대한 정보에 접근할 수 없습니다(타입 지워짐).

분산:

  • 공변성(out T): 서브타입을 사용할 수 있게 해줍니다.
  • 반공변성(in T): 슈퍼타입을 사용할 수 있게 해줍니다.
  • 불변성: 분산 수정자가 없는 타입.

공변성 예:

interface Producer<out T> { fun produce(): T }

반공변성 예:

interface Consumer<in T> { fun consume(item: T) }

자바와의 차이:

  • 문법이 더 명시적이고 간결합니다(out? extends, in? super 대신 사용됨).
  • 와일드카드 타입(?)이 없습니다, 오직 in/out만 존재합니다.
  • 제너릭 파라미터의 인스턴스를 생성하는 것이 금지됩니다.

속임수 질문

질문: "코틀린에서 배열 배열(Array<Array<Int>>)을 Array<out Array<Int>>로 선언할 수 있나요? 그리고 그러한 배열에 값을 기록하려고 하면 무엇이 발생하나요?"

답변: 네, Array<out Array<Int>>로 선언할 수 있지만 그러한 배열은 읽기 전용(read-only)이 됩니다:

val arr: Array<out Array<Int>> = Array(1) { Array(1) { 0 } } arr[0] = arrayOf(1, 2, 3) // 컴파일 오류!

값을 기록하려고 하면 오류가 발생합니다 — out 파라미터가 있는 제너릭 배열은 요소를 기록하는 것을 허용하지 않기 때문에 타입 안전성이 위반될 수 있습니다.

주제의 세부사항에 대한 무지로 인한 실제 오류 사례


이야기

팀에서는 out 파라미터 타입을 가진 제너릭 객체 배열을 만들려고 했는데, 이후 set(index, value)를 통해 값을 넣으려고 했습니다. 코드는 컴파일되었으나 런타임에서 오류가 발생하여 여러 기능이 작동하지 않았습니다.


이야기

자바에서 코틀린으로 라이브러리를 마이그레이션할 때 와일드카드 타입(? extends ...)을 남겨 두었고, 코틀린에서 단순히 타입을 변경하지 않고 out/in으로 복사했습니다. 결과적으로 컴파일이 실패했으며, "통과" 시 런타임 오류가 발생하여 디버깅 과정이 복잡해졌습니다.


이야기

사용자 정의 클래스와 함께 in/out 분산을 사용했지만 수정자를 혼동하여 interface Stack<in T> 대신 Stack<out T>로 선언했습니다. 이는 스택에서 요소를 반환할 수 없게 만들었으며, 메서드 시그니처가 시스템 out/in 계약을 위반했습니다.