Programmingバックエンド開発者

Javaにおけるジェネリクスの生成と使用に関する重要な側面について教えてください。安全に使用するために知っておくべきポイントは何ですか?

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

回答.

ジェネリクスは、型パラメータを持つクラス、インターフェース、メソッドを作成することを可能にし、コンパイル時に型のチェックを行い、ClassCastExceptionを回避するのに役立ちます。

主な特徴と注意点:

  • 型消去 (Type Erasure): コンパイル時に型パラメータの情報が消去されるため、例えばジェネリック型の配列を作成することはできません: new List<String>[10] — コンパイルエラー。
  • 使用に関する制限:
    • パラメータ化された型のインスタンスを作成することはできません: T obj = new T();
    • パラメータ化された型でinstanceofを使用することはできません: if(obj instanceof List<String>) — エラー。
    • パラメータ化された型の静的フィールドを作成することはできません。
  • ワイルドカード: ? extends T — 単調増加(読み取り)、? super T — 単調減少(書き込み)。
  • PECS原則 (Producer Extends, Consumer Super): 読み取りのみが必要な場合はextendsを、書き込みが必要な場合はsuperを使用します。

例:

// 読み取りのための単調増加アプローチ void printNumbers(List<? extends Number> numbers) { for (Number n : numbers) { System.out.println(n); } } // 書き込みのための単調減少アプローチ void addIntegers(List<? super Integer> list) { list.add(10); }

トリックのある質問。

質問: 「List<Object>とList<?>の違いは何ですか?List<?>に任意のオブジェクトを追加できますか?」

回答: いいえ、List<?>には何も追加できません(nullを除く)、コンパイラはそこにどの型パラメータがあるかを知らないからです。一方、List<Object>には任意のオブジェクトを追加できます。

例:

List<?> list1 = new ArrayList<String>(); // list1.add("test"); // コンパイルエラー! List<Object> list2 = new ArrayList<>(); list2.add("test"); // OK

トピックに関する理解不足から生じた実際のエラーの例。


ストーリー

開発チームは、パラメータ化された型T[]に基づくキャッシュを実装しようとしました。型消去とジェネリック型の配列を作成できないことから、解決策は期待通りに機能しませんでした:得られたのはObject[]であり、runtimeキャスト時にClassCastExceptionを引き起こしました。


ストーリー

あるマイクロサービスで、開発者はList<?>をパラメータとして使用するレシーバーを実装しようとし、コレクションの変更を試みました。これによりコンパイルエラーが発生し、PECSに従ってロジックをリファクタリングする必要があり、リリースの遅延を引き起こしました。


ストーリー

外部システムとの統合プロジェクトで、開発者は未処理のローフィールドを通じて異なる型のコレクションを上書きするというエラーを犯し、List list = new ArrayList<String>()としたため、ClassCastExceptionが発生し、本番環境で他の型にキャストしようとした際にサービスがクラッシュしました。