Programmingバックエンド開発者

Kotlinでは、genericsはどのように実装されていますか?制限は何ですか?不変性、共変性、逆変性はどのように機能し、Javaのジェネリクスとはどのように異なりますか?使用例を挙げてください。

Hintsage AIアシスタントで面接を突破

回答

GenericsはKotlinで汎用的で型安全なデータ構造や関数を作成することを可能にします。主な特徴は、Kotlinがコンパイル時にジェネリック型システムを実装しており、Javaと同様ですが、より厳格な型付けとvariance(共変性と逆変性)の拡張構文を持っていることです。

ジェネリクスの制限:

  • 型パラメータはデフォルトで不変です。
  • 型パラメータのインスタンスを作成することはできません (T()は禁止されています)。
  • 実行時にジェネリックの型情報にアクセスできません(型消去)。

Variance:

  • 共変性 (out T): サブタイプを使用できます。
  • 逆変性 (in T): スーパタイプを使用できます。
  • 不変性: variance修飾子のない型。

共変性の例:

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

逆変性の例:

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

Javaとの違い:

  • 構文がより明示的で簡潔です(out? extendsの代わり、in? superの代わり)。
  • ワイルドカード型がありません(?、ただしin/outのみ)。
  • ジェネリックパラメータのインスタンス作成が禁止されています。

ひっかけ問題

質問: 「Kotlinでは、配列の配列(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)を介して値を設定しようとしました。コードはコンパイルされましたが、ランタイムでエラーが発生し、いくつかの関数が機能しなくなりました。


説話

一度、JavaからKotlinにライブラリを移行する際、ワイルドカード型(? extends ...)を残し、Kotlinでは単に型をout/inに変更せずにコピーしました。結果は、コンパイルが失敗し、"遍歴"中にエラーがランタイムで発生し、デバッグプロセスが複雑になりました。


説話

ユーザー定義クラスでin/out変異を使用しましたが、修飾子を間違え、interface Stack<in T>をStack<out T>の代わりに宣言しました。これにより、スタックから要素を戻すことができなくなる結果となり、メソッドのシグネチャがsystem out/in契約を破ることになりました。