合ってたらしい
型が合ったぁぁ - 設計と実装の狭間で。のフォローアップ。
オーバーライドの共変戻り値型と、パラメータ化型のサブタイピングを組み合わせた感じです。
共変戻り値型
8.4.8 Inheritance, Overriding, and Hiding
一言で言うと、オーバーライドしたメソッドは、親クラスのメソッドのサブタイプを返してOK。
interface A { Number f(); } interface B extends A { Integer f(); // ok }
サブタイピング
パラメータ化型が絡むと多少厄介です。ただ、使えると便利なのでぜひ。
まず、演算子の定義。
- 「U
- 「U <= V」→ UはVに含まれる
とりあえずはまりやすいポイントは、U <: V のときに A <: A
僭越ながらサンプルを書くと、
public class Main { public void main(String[] args) { NumberedA np = ...; List<NumberedB> fs1 = np.nestedb(); // もしこれがOKだったとき List<B<Integer, Long>> fs2 = np.nestedb(); assert(fs1 == fs2); // fs1, fs2は同一のリストとして fs2.add(new B<Integer, Long>()); // fs2(=fs1)にはBが入る NumberedB b = fs1.get(0); // fs1(=fs2)から取り出すとNumberedBではなくB<..> } }
といった具合に一貫性が保てなくなります。
これだとジェネリクスが使い物にならないので、Javaでは型の「範囲」を表現するためにワイルドカードが導入されてます。ワイルドカードは型そのものではなく、「この型のサブクラス」とか「この型のスーパークラス」とかいう境界の指定をします。無指定の場合は全部。
パラメータ化型は、型パラメータが「より狭い範囲」を表現しているときにサブタイプとみなすことができます。この「より狭い範囲」を「型の包括」と呼び、U <= V を「UはVに含まれる」といいます。
んで、G< U> <: G< V> が成り立つには、UはVより狭い範囲でなければなりません。サブタイピングが「特化」だとすると、より狭い範囲を指定することは直観的にずれてない感じ。
- まず、?はすべての型の範囲を表現しますので、すべての型が含まれます
- T <= ? (G
<: G>)
- T <= ? (G
- ワイルドカードに境界を指定した場合、その境界型はワイルドカードに含まれます
- T <= ? extends T (G
<: G extends T>) - T <= ? super T (G
<: G super T>)
- T <= ? extends T (G
- ワイルドカードにextends(上限)境界を指定した場合、境界型のサブタイプはワイルドカードに含まれます
- U <: V のとき、 U <= ? extends V (G <: G extends V>)
- ワイルドカードにextends(上限)境界を指定した場合、サブタイプを境界型に持つワイルドカードは、元のワイルドカードに含まれます
- U <: V のとき、 ? extends U <= ? extends V (G extends U> <: G extends V>)
覚えるのもばからしいので、こう考えると楽な感じ。
まず、すべての型を、上限と下限を持つ型の範囲([上限, 下限])と考えてしまいます。
はすべての型のサブタイプ - Objectはすべての型のスーパータイプ
- T は [T, T] (? extends T super T) の省略形
- ? は [Object,
] (? extends Object super ) の省略形 - ? extends T は [T,
] (? extends T super ) の省略形 - ? super T は [Object, T] (? extends Object super T) の省略形
このうえで、
- (? extends U, super V)
- S
数直線上に並べてみると◎。[V, U] <: [T, S] なので、S〜Tの間にU〜Vが来ています。もちろん、? ([Object,
Object :> T :> V :> U :> S :> <null> <=[V, U]=> <========[T, S]========> <==============[Object, <null>]=============>
本題
やっと前置き終了。
List
List< ? extends B
- NumberedB <: B
なので、NumberedB <= ? extends B - NumberedB <= ? extends B
なので、List <: List extends B >
- NumberedB <= ? extends B
ただ、List< ? extends B
イディオム的に。
- メソッドの戻り値のListに対して、読み書きを行う場合→共変させない(=List
の形でそのままにしとく) - メソッドの戻り値のListに対して、変更を行わない場合→List< ? extends T>の形にしとく
- メソッドの戻り値のListに対して、変更のみを行う場合→List< ? super T>の形にしとく
こうすると、オーバーライドするときに便利です。