メソッド呼び出し時の型推論とか

自分めも。途中からメソッド呼び出し時の型推論についての話題になっても気にしない。タイトルも直した。

クラスインスタンス生成式の型の部分にはtype wildcardは書けない。

List<?> list = new ArrayList<?>();
//                          ^^^ - ERROR

なんとなくこうは書きたくない。

List<?> list = new ArrayList<Object>();
//                          ^^^^^^^^ - Object?

メソッドの型推論に任せると勝手に < Object > を作ってくれる。

public static void main(String[] args) {
    ArrayList<?> obj = newArrayList();
}
private static <T> ArrayList<T> newArrayList() {
    return new ArrayList<T>();
}

じつはこれ、それなりに高度な*1型推論がかかっている(太字の部分はこちらで付けました)。

If any of the method's type arguments were not inferred from the types of the actual arguments, they are now inferred as follows.

  • If the method result occurs in a context where it will be subject to assignment conversion (§5.2) to a type S, then let R be the declared result type of the method, and let R' = R[T1 = B(T1) ... Tn = B(Tn)] where B(Ti) is the type inferred for Ti in the previous section, or Ti if no type was inferred.

Then, a set of initial constraints consisting of:

  • the constraint S >> R', provided R is not void; and
  • additional constraints Bi[T1 = B(T1) ... Tn = B(Tn)] >> Ti, where Bi is the declared bound of Ti,

is created and used to infer constraints on the type arguments using the algorithm of section (§15.12.2.7). Any equality constraints are resolved, and then, for each remaining constraint of the form Ti

  • Otherwise, the unresolved type arguments are inferred by invoking the procedure described in this section under the assumption that the method result was assigned to a variable of type Object.
15.12.2.8 Inferring Unresolved Type Arguments

まぁ誰も上を読む人はいないでしょうけど、一言でいえば
「メソッド起動時の型推論で実引数から推論されていない型があって、さらにメソッドの結果をどこかの変数に代入しようとしていたばあい、その変数に代入できるようにそれぞれの型変数を推論してみる。違ったら型変数はObjectに*2しときますね」
ということ。

下記のように書いてもOKなのは、ArrayList< T >がArrayList< String >に代入可能なように頑張って推論してくれるため。

public static void main(String[] args) {
    ArrayList<String> obj = newArrayList();
}
private static <T> ArrayList<T> newArrayList() {
    return new ArrayList<T>();
}

ここのポイントは
「If the method result occurs in a context where it will be subject to assignment conversion (§5.2)」という一文。厳密さは下がるけどすごく簡単にいえば「メソッドの結果をどこかの変数に代入する場合」のことを指す。

// メソッドの引数にArrayList<String>をとる
private static void setArrayList(ArrayList<String> al) {
}
public static void main(String[] args) {
    // これはOK
    ArrayList<String> a = newArrayList();
    setArrayList(a);
    
    // これはNG
    setArrayList(newArrayList());
    
    // これもNG
    ArrayList<String> b = (ArrayList<String>) newArrayList();
}
private static <T> ArrayList<T> newArrayList() {
    return new ArrayList<T>();
}

メソッドの引数に渡す場合や、キャストする場合は代入変換*3が適用されないのでコンパイルエラーになります。Collections.< String >emptyList()とか書かなきゃいけなくなるアレ。これらはそれぞれメソッド起動変換*4、キャスト変換*5という別の名前がついてます。

代入じゃない場合は未解決の型変数がことごとくObjectに推論されるので、上記の下二つは型エラーになってます。こういう細かなところでJavaジェネリクスが嫌われている気はする。

*1:≒実装が面倒な

*2:厳密には、型変数TがObjectに代入できるという制約を型推論に導入する。T extends CharSequenceだったらTはちゃんとObjectじゃなくてCharSequenceになる

*3:5.2 Assignment Conversion

*4:5.3 Method Invocation Conversion

*5:5.5 Casting Conversion