Javaにおける一般化されたターゲット型推論
1.はじめに
型推論は、ジェネリックの導入を補完するためにJava 5で導入され、以降のJavaリリースで大幅に拡張されました。これは一般化ターゲット型推論とも呼ばれます。
このチュートリアルでは、コードサンプルを使用してこの概念を探ります。
2.ジェネリックス
ジェネリックスは、型安全性の向上、型キャストエラーの回避、および汎用アルゴリズムなど、多くの利点をもたらしました。あなたはこのリンクでジェネリックについてもっと読むことができます:/java-generics[article]。
ただし、
総称の導入により、型パラメータを渡す必要があるため、定型コードを記述する必要がありました
。例をいくつか示します。
Map<String, Map<String, String>> mapOfMaps = new HashMap<String, Map<String, String>>();
List<String> strList = Collections.<String>emptyList();
List<Integer> intList = Collections.<Integer>emptyList();
3. Java 8より前の型推論
不必要なコードの冗長性を減らすために、型推論がJavaに導入されました。これは、文脈情報に基づいて式の不特定のデータ型を自動的に推定するプロセスです
。
これで、パラメータの型を指定せずに同じジェネリック型とメソッドを呼び出すことができます。コンパイラーは必要に応じて自動的にパラメーター・タイプを推測します。
新しい概念を使って同じコードを見ることができます。
List<String> strListInferred = Collections.emptyList();
List<Integer> intListInferred = Collections.emptyList();
上記の例では、期待される戻り型
List <String>
と
List <Integer>
に基づいて、コンパイラはtypeパラメータを次の汎用メソッドに推論することができます。
public static final <T> List<T> emptyList()
ご覧のとおり、結果のコードは簡潔です。 typeパラメータが推論できるのであれば、今度は一般的なメソッドを通常のメソッドとして呼び出すことができます。
Java 5では、上記のように特定のコンテキストで型推論を行うことができました。
Java 7では、実行可能なコンテキストが拡張されました。ダイヤモンド演算子
<>
が導入されました。あなたはこのリンクでダイヤモンド演算子についてもっと読むことができます:/java-diamond-operator[article]。
さて、
我々は代入文脈のジェネリッククラスコンストラクタに対してこの操作を実行できます。
その一例です。
Map<String, Map<String, String>> mapOfMapsInferred = new HashMap<>();
ここでは、Javaコンパイラは期待される代入型を使用して、typeパラメータを
HashMap
コンストラクタに推論します。
4.一般化されたターゲット型推論 – Java 8
Java 8では、型推論の範囲がさらに拡張されました。この拡張された推論機能を一般化ターゲットタイプ推論と呼びます。技術的な詳細はhttps://docs.oracle.com/javase/specs/jls/se8/html/jls-18.html[こちら]をご覧ください。
Java 8ではLambda式も導入されました。
ラムダ式には明示的な型はありません。それらの型は、コンテキストまたは状況のターゲット型を調べることによって推測されます。
式のTarget-Typeは、式が現れる場所に応じてJavaコンパイラが予期するデータ型です。
Java 8は、メソッドコンテキストでTarget-Typeを使用した推論をサポートします。明示的な型引数を指定せずに汎用メソッドを呼び出すと、コンパイラはメソッド呼び出しと対応するメソッド宣言を調べて、呼び出しを適用可能にする型引数を判断できます。
コード例を見てみましょう。
static <T> List<T> add(List<T> list, T a, T b) {
list.add(a);
list.add(b);
return list;
}
List<String> strListGeneralized = add(new ArrayList<>(), "abc", "def");
List<Integer> intListGeneralized = add(new ArrayList<>(), 1, 2);
List<Number> numListGeneralized = add(new ArrayList<>(), 1, 2.0);
コードでは、
ArrayList <>
はtype引数を明示的に提供しません。そのため、コンパイラはそれを推測する必要があります。まず、コンパイラはaddメソッドの引数を調べます。その後、さまざまな呼び出しで渡されたパラメータを調べます。
-
このメソッドがこれらの呼び出しに適用されるかどうかを判断するために、呼び出し適用可能性推論分析が実行されます。多重定義のために複数の方法が適用可能な場合、コンパイラは最も具体的な方法を選択します。
次に、
コンパイラは、型引数を決定するために
呼び出し型推論分析を実行します。
** この分析では、予想されるターゲット型も使用されます。 3つのインスタンスの引数をArrayList <String>
、
ArrayList <Integer>
、および
ArrayList <Number> __として推測します。
Target-Type推論により、ラムダ式パラメータの型を指定しないようにすることができます。
List<Integer> intList = Arrays.asList(5, 2, 4, 2, 1);
Collections.sort(intList, (a, b) -> a.compareTo(b));
List<String> strList = Arrays.asList("Red", "Blue", "Green");
Collections.sort(strList, (a, b) -> a.compareTo(b));
ここで、パラメータ
a
と
b
は明示的に定義された型を持ちません。それらの型は、最初のLambda式では
Integer
、2番目の式では
String
と推測されます。
5.まとめ
このクイック記事では、型推論について概説しました。ジェネリックとLambda Expressionを使用すると、簡潔なJavaコードを作成できます。
いつものように、完全なソースコードはhttps://github.com/eugenp/tutorials/tree/master/core-java-8[Githubに追加]を見つけることができます。