型推論の正解が分からない
自分めも。だけどわかる人教えてください。
public static void main(String...args) { Integer a = 1; Long b = 2; Object result = method(a, b); // resultの型は? } static <T> T method(T a, T b) { return null; }
このmethod(a, b)の型推論で、Tはどのような型に算出されるかという問題。
15.12.2.8 Inferring Unresolved Type Argumentsを追いかけながら進んでいく感じ。ただし、型推論の後半部分だけ。
おさらいしておくと、Integer, Long はそれぞれ下記のような宣言。
class Integer extends Number implements Comparable<Integer> class Long extends Number implements Comparable<Long> class Number extends Object implements Serializable
おおざっぱに省略すると、T :> Integer, T :> Long という制約になる。なので、Tの型は{Integer, Long}の最小限の上限境界型と一致するはず。これは、Integer, Longの共通のスーパータイプであり、さらにそれらの中で最も限定的なもののこと。
これは最終的に、least upper bound (lub: 最小限の上限境界)が求められればいい。{Integer, Long}の最小の上限境界型をlub({Integer, Long}), この結果をRとおく。
Java言語仕様第三版(和訳)の406ページあたりから追いかけてみる。
それぞれの型の super type set (ST: スーパータイプの集合)を算出する。これはInteger, Longのextendsとimplementsを追いかけて行って出現するものを列挙するだけ。
ST(Integer) = { Integer, Number, Object, Comparable<Integer>, Serializable } ST(Long) = { Long, Number, Object, Comparable<Long>, Serializable }
STにイレイジャ変換を適用して、erased super type set (EST: 型を消去したスーパータイプの集合)を算出する。STから<>を取り除いただけ。
EST(Integer) = { Integer, Number, Object, Comparable, Serializable } EST(Long) = { Long, Number, Object, Comparable, Serializable }
ESTの共通部分集合をとって、erased candidate set (EC: 型を消去した候補の集合)を算出する。Integer, Longがそれぞれ消える。
EC({Integer, Long}) = EST(Integer) & EST(Long) = { Number, Object, Comparable, Serializable }
ECのうち他の型のスーパータイプになるものを除去して、minimal erased candidate set (MEC: 型を消去した候補の最小限の集合)を算出する。Object :> Number, Serializable :> Number なのでそれぞれ消える。
MEC({Integer, Long}) = { Number, Comparable }
MECのそれぞれの型について型引数を復元し、invocation (Inv: 型を復元した候補の最小限の集合*1 )を算出する。これは ST → EST の逆をとればいいだけ。
Inv(Number, {Integer, Long}) = { Number // = { Number, Number } } Inv(Comparable, {Integer, Long}) = { Comparable<Integer>, Comparable<Long> }
Invの要素が持つ型引数から、それらを包括する最小限の型、least containing invocation (lci: 型を復元した候補を包括する最小限の型*2 *3 )を算出する。Numberは復元するまでもないのでそのまま利用、Comparableが{Integer, Long}で異なるので後述のlctaを使って算出する。
lci({Number}) = Number lci({Comparable<Integer>, Comparable<Long>}) = Comparable<lcta({Integer, Long})>
lciの途中に出てきたleast containing type argument(lcta: 型引数の最小の上限境界*4 )を算出する。定義「lcta({U, V}) = ? extends lub({U, V}), where U != V」を利用する。
lcta({Integer, Long}) = ? extends lub({Integer, Long})
と、ここで最初に求めたかったlub({Integer, Long})が再度出現する。これはとりあえず最初に定義したRとおいておく。
これで、先ほどのlciが計算できる。
lci({Comparable<Integer>, Comparable<Long>}) = Comparable<lcta({Integer, Long})> = Comparable<? extends lub({Integer, Long})> = Comparable<? extends R>
それぞれのlciの共通型(intersection type: 4.9 Intersection Types)が、最終的なlub({Integer, Long})の結果となる。途中でglb(greatest lower bound, 最大限の下限境界)の計算があるけど、これは共通型を計算すれば終わりなので割愛。
lub({Integer, Long}) = glb({ lci({Number}), lci({Comparable<Integer>, Comparable<Long>}) }) = glb({ Number, Comparable<? extends R> }) = Number & Comparable<? extends R>
つまり、
lub({Integer, Number}) as R = Number & Comparable<? extends R>
となる。
これはRを含む無限型なので、表示できない。
Number & Comparable<? extends Number & Comparable<? extends Number & Comparable<? extends Number & Comparable<? extends Number & Comparable<? extends Number & Comparable<? extends Comparable<...>>>>>>>
最初に戻ると、下記のresultはどうなるかということだった。
public static void main(String...args) { Integer a = 1; Long b = 2; Object result = method(a, b); // resultの型は? } static <T> T method(T a, T b) { return null; }
これのmethodの総称宣言に、先ほどの型を上限境界として加えてやる。
public static void main(String...args) { Integer a = 1; Long b = 2; Object result = method(a, b); } // 上限境界を設定 static <T extends Number & Comparable<? extends T>> T method(T a, T b) { return null; }
しかしなんかうまくいかない(見やすいように加工済み)。
..:method(T,T) を (Integer,Long) に適用できません Object result = method(a, b); ^ エラー 1 個
なんだろう…。どこかに見落としがあったらツッコミお願いします。
ちなみに、上記はSunのjavacでやった結果です。EclipseのJDTでやるともっと精度が悪いので、後ほど言及します。
追記
一つ上にjavacとJDTのエントリを書いた。
*1:言語仕様では「起動」と訳していた
*2:言語仕様では「最小限の包括起動」と訳していた
*3:包括については合ってたらしい - しげるメモとかに
*4:言語仕様では「最小限の包括型引数」と訳していた