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のにあります。