ジェネリッククラスはパラメータとして型を1つ受け取るクラスです。それらはコレクションクラスで特に役立ちます。
ジェネリッククラスの定義
ジェネリッククラスは角カッコ[]
の中にパラメータとして型を1つ受け取ります。
型パラメータの識別子として文字A
を使う習慣がありますが、任意のパラメータ名を使うことができます。
class Stack[A] {
private var elements: List[A] = Nil
def push(x: A): Unit =
elements = x :: elements
def peek: A = elements.head
def pop(): A = {
val currentTop = peek
elements = elements.tail
currentTop
}
}
このStack
クラスの実装はパラメータとして任意の型A
を受け取ります。
これはメンバーのリストvar elements: List[A] = Nil
が型A
の要素のみを格納できることを意味します。
手続きdef push
は型A
のオブジェクトのみを受け取ります
(注: elements = x :: elements
は、x
を現在のelements
の先頭に追加した新しいリストをelements
に割り当て直します)。
ここで Nil
は空の List
であり、 null
と混同してはいけません。
使い方
ジェネリッククラスを使うには、角カッコの中にA
の代わりに型を入れます。
val stack = new Stack[Int]
stack.push(1)
stack.push(2)
println(stack.pop) // prints 2
println(stack.pop) // prints 1
インスタンスstack
はIntのみを受け取ることができます。
しかしながら、型がサブタイプを持つ場合、それらは以下のように渡すことができます。
class Fruit
class Apple extends Fruit
class Banana extends Fruit
val stack = new Stack[Fruit]
val apple = new Apple
val banana = new Banana
stack.push(apple)
stack.push(banana)
クラスApple
とBanana
は共にFruit
を継承しています。そのためFruit
のスタックにはapple
とbanana
のインスタンスを追加できます。
注意: ジェネリック型のサブタイプは*非変(invariant)*です。つまりStack[Char]
型の文字スタックがあるとき、それをStack[Int]
型の整数スタックとして使うことはできません。文字スタックに整数を入れることはできるので、このことは変に思えるかもしれません。結論としては、B = A
の場合に限り、Stack[A]
はStack[B]
の唯一のサブタイプとなります。これでは制限が強いので、ジェネリック型のサブタイプの振る舞いをコントロールするために、Scalaは型引数アノテーションの仕組みを提供します。