Javaにおける一般化されたターゲット型推論

Javaの一般化されたターゲットタイプの推論

1. 前書き

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

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

2. ジェネリックス

ジェネリックは、型の安全性の向上、型キャストエラーの回避、汎用アルゴリズムなど、多くの利点をもたらしました。 このarticleでジェネリックスについてもっと読むことができます。

ただし、generics resulted in the necessity of writing boilerplate code due to the need to pass type parametersの導入。 例をいくつか示します。

Map> mapOfMaps = new HashMap>();
List strList = Collections.emptyList();
List intList = Collections.emptyList();

3. Java 8より前の型推論

不必要なコードの冗長性を減らすために、Javaに型推論が導入されました。 is the process of automatically deducing unspecified data types of an expression based on the contextual information.

これで、パラメータタイプを指定せずに、同じジェネリックタイプとメソッドを呼び出すことができます。 コンパイラーは、必要なときにパラメーターのタイプを自動的に推測します。

新しいコンセプトを使用して同じコードを見ることができます:

List strListInferred = Collections.emptyList();
List intListInferred = Collections.emptyList();

上記の例では、期待される戻り型List<String>およびList<Integer>に基づいて、コンパイラーは型パラメーターを次の汎用メソッドに推測できます。

public static final  List emptyList()

ご覧のとおり、結果のコードは簡潔です。 Now, we can call generic methods as an ordinary method if the type parameter can be inferred.

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

Java 7は、実行可能なコンテキストを拡張しました。 ダイヤモンド演算子<>が導入されました。 このarticleで、ダイヤモンド演算子の詳細を読むことができます。

さて、we can perform this operation for generic class constructors in an assignment context. そのような例の1つは次のとおりです。

Map> mapOfMapsInferred = new HashMap<>();

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

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

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

Java 8はLambda Expressionsも導入しました。 Lambda Expressions do not have an explicit type.  Their type is inferred by looking at the target type of the context or situation.式のTarget-Typeは、式が表示される場所に応じてJavaコンパイラが予期するデータ型です。

Java 8は、メソッドコンテキストでTarget-Typeを使用した推論をサポートしています。 When we invoke a generic method without explicit type arguments, the compiler can look at the method invocation and corresponding method declarations to determine the type argument (or arguments) that make the invocation applicable.

サンプルコードを見てみましょう。

static  List add(List list, T a, T b) {
    list.add(a);
    list.add(b);
    return list;
}

List strListGeneralized = add(new ArrayList<>(), "abc", "def");
List intListGeneralized = add(new ArrayList<>(), 1, 2);
List numListGeneralized = add(new ArrayList<>(), 1, 2.0);

コードでは、ArrayList<>はtype引数を明示的に提供していません。 そのため、コンパイラはそれを推測する必要があります。 最初に、コンパイラはaddメソッドの引数を調べます。 次に、異なる呼び出しで渡されたパラメーターを調べます。

It performs invocation applicability inference analysis to determine whether the method applies to these invocations。 オーバーロードのために複数のメソッドが適用可能な場合、コンパイラは最も具体的なメソッドを選択します。

次に、the compiler performs invocation type inference analysis to determine the type arguments.The expected target types are also used in this analysis。 3つのインスタンスの引数をArrayList<String>ArrayList<Integer>ArrayList<Number>として推定します。

Target-Type推論により、ラムダ式パラメーターのタイプを指定しないようにできます。

List intList = Arrays.asList(5, 2, 4, 2, 1);
Collections.sort(intList, (a, b) -> a.compareTo(b));

List strList = Arrays.asList("Red", "Blue", "Green");
Collections.sort(strList, (a, b) -> a.compareTo(b));

ここで、パラメーターaおよびbには、明示的に定義されたタイプがありません。 それらのタイプは、最初のラムダ式ではIntegerとして、2番目の式ではStringとして推測されます。

5. 結論

この簡単な記事では、ジェネリックおよびLambda Expressionとともに簡潔なJavaコードを記述できるようにするType Inferenceを確認しました。

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