1. 序章

型推論は、ジェネリックスの導入を補完するためにJava 5で導入され、次のJavaリリースで大幅に拡張されました。これは一般化されたターゲット型推論とも呼ばれます。

このチュートリアルでは、コードサンプルを使用してこの概念を探ります。

2. ジェネリックス

ジェネリックスは、型の安全性の向上、型キャストエラーの回避、ジェネリックアルゴリズムなど、多くの利点を提供してくれました。 ジェネリックスについて詳しくは、この記事をご覧ください。

ただし、ジェネリックの導入により、タイプパラメータを渡す必要があるため、ボイラープレートコードを記述する必要がありました。 いくつかの例は次のとおりです。

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. Java8より前の型推論

不必要なコードの冗長性を減らすために、型推論がJavaに導入されました。これは、コンテキスト情報に基づいて式の不特定のデータ型を自動的に推測するプロセスです。

これで、パラメータータイプを指定せずに、同じ汎用タイプとメソッドを呼び出すことができます。 コンパイラは、必要に応じてパラメータタイプを自動的に推測します。

新しい概念を使用して同じコードを見ることができます。

List<String> strListInferred = Collections.emptyList();
List<Integer> intListInferred = Collections.emptyList();

上記の例では、期待収益タイプに基づいていますリストリスト 、コンパイラは、typeパラメータを次の汎用メソッドに推測できます。

public static final <T> List<T> emptyList()

ご覧のとおり、結果のコードは簡潔です。 これで、型パラメーターが推測できる場合は、通常のメソッドとして汎用メソッドを呼び出すことができます。

Java 5では、上記のように特定のコンテキストで型推論を行うことができます。

Java 7は、それを実行できるコンテキストを拡張しました。 ダイヤモンドオペレーターを紹介しました <> 。 この記事でダイヤモンド演算子の詳細を読むことができます。

今、 この操作は、割り当てコンテキストの汎用クラスコンストラクターに対して実行できます。 そのような例の1つは次のとおりです。

Map<String, Map<String, String>> mapOfMapsInferred = new HashMap<>();

ここで、Javaコンパイラは、予想される割り当てタイプを使用して、タイプパラメータをHashMapコンストラクタに推測します。

4. 一般化されたターゲット型推論– Java 8

Java 8は、型推論の範囲をさらに拡大しました。 この拡張された推論機能を、一般化されたターゲットタイプの推論と呼びます。 技術的な詳細ここを読むことができます。

Java8では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つのインスタンスの引数を次のように推測します。 配列リスト 配列リスト配列リスト

ターゲットタイプの推論により、ラムダ式パラメーターのタイプを指定しないことができます。

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コードを記述できる型推論について説明しました。

いつものように、完全なソースコードはGithubにあります。