ジェネリックは、 2004 年にバージョンJ2SE 5.0内でJava プログラミング言語に追加されたジェネリックプログラミングの機能です。これらは、Java の型システムを拡張して、「コンパイル時の型安全性を提供しながら、さまざまな型のオブジェクトで型またはメソッドを操作できるようにする」ように設計されています。[1]コンパイル時の型安全性の側面では、パラメトリック多態関数がJava 仮想マシンに実装されていないことが要求されます。この場合、型の安全性は不可能だからです。[2] [3]
Javaコレクション フレームワークは、コレクション インスタンスに格納されるオブジェクトのタイプを指定するためのジェネリックをサポートします。
1998年、Gilad Bracha、Martin Odersky、David Stoutamire、Philip Wadlerは、ジェネリック型をサポートするJava言語の拡張であるGeneric Javaを作成しました。[4] Generic Javaはワイルドカードの追加とともにJavaに組み込まれました。
階層と分類
Java言語仕様によると: [5]
- 型変数は修飾されていない識別子です。型変数は、ジェネリック クラス宣言、ジェネリック インターフェイス宣言、ジェネリック メソッド宣言、およびジェネリック コンストラクター宣言によって導入されます。
- クラスがジェネリックであるのは、1つ以上の型変数を宣言している場合です。[6]パラメータとして機能する1つ以上の型変数を定義します。[7]ジェネリッククラスの宣言は、型パラメータセクションの可能な呼び出しごとに1つずつ、パラメータ化された型のセットを定義します。これらのパラメータ化された型はすべて、実行時に同じクラスを共有します。
- インターフェースがジェネリックであるのは、1つ以上の型変数を宣言している場合です。[7]パラメーターとして機能する1つ以上の型変数を定義します。[7]ジェネリックインターフェース宣言は、型パラメーターセクションの可能な呼び出しごとに1つずつ、型のセットを定義します。パラメーター化されたすべての型は、実行時に同じインターフェースを共有します。
- メソッドがジェネリックであるのは、1つ以上の型変数を宣言している場合です。[8]これらの型変数は、メソッドの正式な型パラメータと呼ばれます。正式な型パラメータリストの形式は、クラスまたはインターフェイスの型パラメータリストと同じです。
- コンストラクターは、コンストラクターが宣言されているクラス自体がジェネリックであるかどうかに関係なく、ジェネリックとして宣言できます。コンストラクターがジェネリックであるのは、1 つ以上の型変数を宣言している場合です。これらの型変数は、コンストラクターの正式な型パラメーターと呼ばれます。正式な型パラメーター リストの形式は、ジェネリック クラスまたはインターフェイスの型パラメーター リストと同じです。
モチベーション
次の Java コード ブロックは、ジェネリックを使用しない場合に発生する問題を示しています。まず、ArrayList型のを宣言します。次に、を にObject追加します。最後に、追加された を取得して にキャストしようとします。これは、一般に任意の文字列を整数にキャストすることはできないため、ロジック エラーとなります。
StringArrayListStringInteger
final List v = new ArrayList (); v . add ( "test" ); // Integer にキャストできない文字列final Integer i = ( Integer ) v . get ( 0 ); // 実行時エラー
java.lang.ClassCastExceptionこのコードはエラーなくコンパイルされますが、 3行目のコードを実行すると実行時例外()がスローされます。このタイプのロジックエラーは、ジェネリック[7]を使用することでコンパイル時に検出でき、ジェネリックを使用する主な動機となっています。[6]パラメータとして機能する1つ以上の型変数を定義します。
上記のコード フラグメントは、ジェネリックを使用して次のように書き直すことができます。
final List < String > v = new ArrayList < String > (); v . add ( "test" ); final Integer i = ( Integer ) v . get ( 0 ); // (型エラー) コンパイル時エラー
String山括弧内の型パラメータは、 が(のジェネリック構成要素の子孫)ArrayListから構成されることを宣言します。ジェネリックを使用すると、 の結果がコンパイラによって生成されたコードによって
として定義されるため、3 行目を特定の型にキャストする必要がなくなります。StringArrayListObjectv.get(0)String
このコードの3行目の論理的な欠陥は、コンパイル時エラーとして検出されます(J2SE 5.0以降)。これは、コンパイラがではなくv.get(0)を返すことを検出するためです。[7]より詳細な例については、参考文献を参照してください。[9]StringInteger
以下は、パッケージ内のインターフェースjava.util.Listとの定義からの抜粋です。
java.util.Iteratorjava.util
インターフェース リスト<E> {
void add ( E x );イテレータ< E > iterator ();
}
インターフェース イテレータ<E> {
E next ();ブール型hasNext ();
}
汎用クラス定義
以下は、マップ内の個々のエントリ (キーと値のマッピング) を表すために使用できる汎用 Java クラスの例です。
パブリッククラスEntry < KeyType 、ValueType > {プライベートfinal KeyTypeキー;プライベートfinal ValueType値;
パブリックエントリ( KeyTypeキー、ValueType値) { this.key =キー; this.value =値; }
公開KeyType getKey () {キーを返す; }
パブリックValueType getValue () {戻り値; }
パブリックString toString () { return "(" +キー+ ", " +値+ ")" ; }
}
このジェネリック クラスは、たとえば次のように使用できます。
final Entry < String , String > grade = new Entry < String , String > ( "Mike" , "A" ); final Entry < String , Integer > mark = new Entry < String , Integer > ( "Mike" , 100 ) ; System.out.println ( " grade: " + grade ) ; System.out.println ( " mark : " + mark ) ;
final Entry < Integer , Boolean > prime = new Entry < Integer , Boolean > ( 13 , true ); if ( prime . getValue ()) { System . out . println ( prime . getKey () + " は素数です。" ); } else { System . out . println ( prime . getKey () + " は素数ではありません。" ); }
出力は次のようになります:
評価: (マイク、A) マーク: (マイク、100) 13は素数です。
ジェネリックメソッド定義
上記のジェネリック クラスを使用したジェネリック メソッドの例を次に示します。
パブリックスタティック<Type> Entry < Type 、Type > を2回( Type値) { return new Entry < Type 、Type > (値、値) ; }
注: 上記のメソッドの最初の部分を削除すると<Type>、シンボルの宣言を表しているため、コンパイル エラー (シンボル "Type" が見つかりません) が発生します。
多くの場合、メソッドのユーザーは型パラメータを指定する必要はなく、推測することができます。
最終的なEntry < String , String >ペア= Entry . twice ( "Hello" );
必要に応じてパラメータを明示的に追加できます。
最終的なEntry < String , String >ペア= Entry . < String > を2 回( "Hello" );
プリミティブ型の使用は許可されておらず、代わりにボックス化されたバージョンを使用する必要があります。
final Entry < int , int > pair ; // コンパイルに失敗します。代わりに Integer を使用してください。
指定されたパラメータに基づいて汎用メソッドを作成することもできます。
public < Type > Type [] toArray ( Type ... elements ) { return elements ; }
このような場合には、プリミティブ型も使用できません。例:
整数[]配列= toArray ( 1 , 2 , 3 , 4 , 5 , 6 );
ダイヤモンド演算子
型推論のおかげで、Java SE 7以降では、十分に近いコンテキストが示唆する1つ以上の型パラメータを含む山括弧のペアを、空の山括弧のペア(ダイヤモンド演算子<>と呼ばれる)に置き換えることができます。[10]したがって、を使用した上記のコード例は、次のように書き直すことができます。
Entry
final Entry < String , String > grade = new Entry <> ( "Mike" , "A" ); final Entry < String , Integer > mark = new Entry < > ( "Mike" , 100 ) ; System.out.println ( " grade : " + grade ) ; System.out.println ( " mark: " + mark ) ;
final Entry < Integer , Boolean > prime = new Entry <> ( 13 , true ); if ( prime . getValue ()) System . out . println ( prime . getKey () + " は素数です。" ); else System . out . println ( prime . getKey () + " は素数ではありません。" );
ワイルドカードを入力する
パラメータ化された型の型引数は、具体的なクラスまたはインターフェースに限定されません。Java では、パラメータ化された型の型引数として「型ワイルドカード」を使用できます。ワイルドカードは、" <?>" 形式の型引数で、オプションで上限または下限を指定できます。ワイルドカードによって表される正確な型は不明であるため、パラメータ化された型を使用するオブジェクトで呼び出すことができるメソッドの型には制限が課されます。
Collection<E>以下は、 a の要素型がワイルドカードによってパラメータ化される
例です。
final Collection <?> c = new ArrayList < String > (); c . add ( new Object ()); // コンパイル時エラーc . add ( null ); // 許可
cの要素型が何を表すかわからないため、これにオブジェクトを追加することはできません。 メソッドは、ジェネリックインターフェースの要素型でadd()ある型 の引数を受け取ります。実際の型引数が の場合、それは何らかの未知の型を表します。 メソッドに渡すメソッド引数値は、この未知の型のサブタイプである必要があります。 それがどのような型であるかがわからないため、何も渡すことができません。唯一の例外はnullです。これはすべての型のメンバーです。[11]ECollection<E>?add()
型ワイルドカードの上限を指定するには、extendsキーワードを使用して、型引数が境界クラスのサブタイプであることを示します。[12]つまり、指定されたリストには、クラスを拡張する未知の型のオブジェクトが含まれていることを意味します。たとえば、リストはまたは である可能性があります。リストから要素を読み取ると が返されます。この場合も、null 要素を追加することもできます。[13]List<? extends Number>NumberList<Float>List<Number>Number
上記のワイルドカードの使用により柔軟性が高まります[12]。これは、具体的な型を型引数として持つ 2 つのパラメータ化された型の間には継承関係がないためです。 もも他方のサブタイプではList<Number>ありませんList<Integer>。ただし、Integerは のサブタイプですNumber。[12]そのため、 をList<Number>パラメータとして取るメソッドは、 の引数を受け入れませんList<Integer>。 受け入れると、Numberではないを に挿入できるようになり、型の安全性に違反します。が のサブタイプであるInteger場合に、型の安全性がどのように違反するかを示す例を次に示します。
List<Integer>List<Number>
final List < Integer > ints = new ArrayList <> (); ints . add ( 2 ); final List < Number > nums = ints ; // 置換ルールに従って、List<Integer> が List<Number> のサブタイプである場合は有効です。nums . add ( 3.14 ); final Integer x = ints . get ( 1 ); // これで 3.14 が Integer 変数に割り当てられます。
ワイルドカードを使用したソリューションは、型の安全性に違反する操作を禁止するため機能します。
final List <? extends Number > nums = ints ; // OK nums.add ( 3.14 ) ; // コンパイル時エラーnums.add ( null ) ; //許可
型ワイルドカードの下限境界クラスを指定するには、superキーワードを使用します。このキーワードは、型引数が境界クラスのスーパータイプであることを示します。したがって、は または を表すことができます。 として定義されたリストから読み取ると、型の要素が返されます。このようなリストに追加するには、 型の要素、 のサブタイプ、またはnull (すべての 型のメンバー) のいずれかが必要です。
List<? super Number>List<Number>List<Object>List<? super Number>ObjectNumberNumber
ジョシュア・ブロック著『Effective Java』に出てくる記憶術PECS(Producer Extends, Consumer Super)は、 Javaでワイルドカード(共変性と反変性に対応)をいつ使用するかを覚えるための簡単な方法を示しています。[12]
throws 節のジェネリック
例外自体はジェネリックにすることはできませんが、ジェネリック パラメータは throws 句に出現できます。
public < T extends Throwable > void throwMeConditional ( boolean conditional , T exception ) throws T { if ( conditional ) { throw exception ; } }
型消去の問題
ジェネリックはコンパイル時に型の正しさがチェックされます。[7]ジェネリックの型情報は、型消去と呼ばれるプロセスで削除されます。[6]たとえば、List<Integer>は非ジェネリック型に変換されますがList、これには通常、任意のオブジェクトが含まれます。コンパイル時のチェックにより、結果のコードが正しい型を使用することが保証されます。[7]
型消去のため、型パラメータは実行時に決定できません。[6]たとえば、実行時に が検査される場合、型消去の前に であったか でArrayListあったかを決定する一般的な方法はありません。多くの人がこの制限に不満を持っています。[14]部分的なアプローチがあります。たとえば、個々の要素がどの型に属するかを決定するために検査される場合があります。たとえば、 に が含まれている場合、その ArrayList は でパラメータ化されている可能性があります(ただし、や など、の任意の親でパラメータ化されている可能性があります)。
ArrayList<Integer>ArrayList<Float>ArrayListIntegerIntegerIntegerNumberObject
この点を示すために、次のコードは「Equal」を出力します。
final List < Integer > li = new ArrayList <> (); final List < Float > lf = new ArrayList <> (); if ( li . getClass () == lf . getClass ()) { // true と評価されますSystem . out . println ( "Equal" ); }
型消去のもう一つの効果は、ジェネリッククラスがThrowable直接的にも間接的にもクラスを拡張できないことである。[15]
パブリッククラスGenericException < T >はExceptionを拡張します
これがサポートされない理由は、型の消去のためです。
try { throw new GenericException < Integer > ( ) ; } catch ( GenericException < Integer > e ) { System.err.println ( " Integer " ) ; } catch ( GenericException < String > e ) { System.err.println ( " String " ) ; }
型の消去により、ランタイムはどの catch ブロックを実行するかがわからなくなるため、これはコンパイラによって禁止されます。
Java ジェネリックはC++ テンプレートとは異なります。Java ジェネリックは、使用されるパラメータ化型の数に関係なく、ジェネリック クラスまたは関数のコンパイル済みバージョンを 1 つだけ生成します。さらに、型情報はコンパイル時に検証され、コンパイルされたコードには含まれないため、Java ランタイム環境は、どのパラメータ化型が使用されているかを知る必要がありません。その結果、パラメータ化型の Java クラスをインスタンス化することは不可能です。インスタンス化にはコンストラクターの呼び出しが必要であり、型が不明な場合はコンストラクターを利用できないためです。
たとえば、次のコードはコンパイルできません。
< T > T instanceiateElementType ( List < T > arg ) { return new T (); //コンパイルエラーが発生します}
実行時にはジェネリック クラスごとにコピーが 1 つしかないため、静的変数は、その型パラメータに関係なく、クラスのすべてのインスタンス間で共有されます。したがって、型パラメータは、静的変数の宣言や静的メソッドでは使用できません。
型消去は、Java SE5以前に書かれたプログラムとの下位互換性を維持するためにJavaで実装されました。[7]
配列との違い
Java の配列 (プリミティブ配列と配列の両方) とジェネリックの間には、いくつかの重要な違いがあります。主な違いの 2 つは、バリアンスと具体化Objectの点における違いです。
共変性、反変性、不変性
ジェネリックは不変ですが、配列は共変です。[6]これは、配列などの非ジェネリックオブジェクトと比較した場合のジェネリックを使用する利点です。[6]具体的には、ジェネリックはコンパイル時の例外をスローして開発者にコードの修正を強制することで、実行時の例外を防ぐのに役立ちます。
たとえば、開発者がObject[]オブジェクトを宣言し、そのオブジェクトを新しいLong[]オブジェクトとしてインスタンス化した場合、コンパイル時の例外はスローされません(配列は共変であるため)。[6]Stringこれにより、コードが正しく記述されているという誤った印象を与える可能性があります。ただし、開発者がこのオブジェクトにを追加しようとするとLong[]、プログラムは をスローしますArrayStoreException。[6]開発者がジェネリックを使用すると、この実行時例外は完全に回避できます。
開発者がCollection<Object>オブジェクトを宣言し、戻り値の型でこのオブジェクトの新しいインスタンスを作成するとArrayList<Long>、Javaコンパイラは(正しく)互換性のない型の存在を示すコンパイル時例外をスローします(ジェネリックは不変であるため)。[6]Collection<Object>したがって、これにより実行時例外の可能性が回避されます。この問題は、代わりにオブジェクトを使用してのインスタンスを作成することで修正できますArrayList<Object>。Java SE7以降のバージョンを使用するコードでは、ダイヤモンド演算子を使用してオブジェクト
Collection<Object>でインスタンス化できます。ArrayList<>
具象化
配列は具体化されており、配列オブジェクトは実行時にその型情報を強制しますが、Javaのジェネリックは具体化されていません。[6]
より正式に言えば、Javaのジェネリック型を持つオブジェクトは非具象化型です。[6]非具象化型とは、実行時の表現がコンパイル時の表現よりも情報が少ない型のことです。[6]
Javaのジェネリック型オブジェクトは、型消去のため非具象化可能だ。[6] Javaはコンパイル時にのみ型情報を強制する。コンパイル時に型情報が検証された後、型情報は破棄され、実行時には型情報は利用できなくなる。[6]
非具象化型の例としては、List<T>や( はジェネリック形式パラメータ)などがある。[6]List<String>T
ジェネリック医薬品に関するプロジェクト
プロジェクトヴァルハラは、 Java 10以降の将来のバージョンに向けて、Javaジェネリックと言語機能の改善を育成する実験的なプロジェクトです。潜在的な機能強化には以下が含まれます。[16]
- ジェネリック特殊化、例: List<int>
- 具象化されたジェネリック。実行時に実際の型を利用できるようになります。
参照
引用
- ^ Javaプログラミング言語
- ^ キャストや null がない場合でも ClassCastException がスローされることがあります。「Java と Scala の型システムは不健全です」(PDF)。
- ^ Bloch 2018、pp. 123–125、第5章項目27:チェックされていない警告を排除する。
- ^ GJ: 汎用Java
- ^ Java 言語仕様、第 3 版、James Gosling、Bill Joy、Guy Steele、Gilad Bracha 著 – Prentice Hall PTR 2005
- ^ abcdefghijklmno Bloch 2018、pp. 126–129、第5章項目28:配列よりもリストを優先する。
- ^ abcdefgh Bloch 2018、pp. 117–122、第5章項目26: 生の型を使用しないでください。
- ^ Bloch 2018、pp. 135–138、第5章項目30:ジェネリックメソッドを優先する。
- ^ Gilad Bracha (2004 年 7 月 5日)。「Java プログラミング言語のジェネリック」(PDF) 。www.oracle.com。
- ^ 「ジェネリックインスタンス作成のための型推論」。
- ^ Gilad Bracha (2004 年 7 月 5日)。「Java プログラミング言語のジェネリック」(PDF)。www.oracle.com。p . 5。
- ^ abcd Bloch 2018、pp. 139–145、第5章項目31:境界付きワイルドカードを使用してAPIの柔軟性を高めます。
- ^ Bracha, Gilad . 「ワイルドカード > ボーナス > ジェネリック」. Java™ チュートリアル. Oracle.
...唯一の例外は null で、これはすべての型のメンバーです...
- ^ Gafter, Neal (2006-11-05). 「Reified Generics for Java」 . 2010-04-20閲覧。
- ^ 「Java言語仕様、セクション8.1.2」。Oracle 。 2015年10月24日閲覧。
- ^ Goetz, Brian. 「Welcome to Valhalla!」OpenJDK メール アーカイブ. OpenJDK . 2014 年8 月 12 日閲覧。
参考文献
- Bloch, Joshua (2018)。『Effective Java: プログラミング言語ガイド』(第 3 版)。Addison- Wesley。ISBN 978-0134685991。
