layout | title | partof | num | next-page | previous-page | assumed-knowledge | redirect_from |
---|---|---|---|---|---|---|---|
tour | Generic Classes | scala-tour | 20 | variances | for-comprehensions | classes unified-types | /tutorials/tour/generic-classes.html |
Generic classes are classes which take a type as a parameter. They are particularly useful for collection classes.
Generic classes take a type as a parameter within square brackets []
. One convention is to use the letter A
as type parameter identifier, though any parameter name may be used.
{% tabs generic-classes-1 class=tabs-scala-version %} {% tab 'Scala 2' for=generic-classes-1 %}
classStack[A] { privatevarelements:List[A] =Nildefpush(x: A):Unit= elements = x :: elements defpeek:A= elements.head defpop():A= { valcurrentTop= peek elements = elements.tail currentTop } }
{% endtab %} {% tab 'Scala 3' for=generic-classes-1 %}
classStack[A]:privatevarelements:List[A] =Nildefpush(x: A):Unit= elements = x :: elements defpeek:A= elements.head defpop():A=valcurrentTop= peek elements = elements.tail currentTop
{% endtab %} {% endtabs %}
This implementation of a Stack
class takes any type A
as a parameter. This means the underlying list, var elements: List[A] = Nil
, can only store elements of type A
. The procedure def push
only accepts objects of type A
(note: elements = x :: elements
reassigns elements
to a new list created by prepending x
to the current elements
).
Nil
here is an empty List
and is not to be confused with null
.
To use a generic class, put the type in the square brackets in place of A
.
{% tabs generic-classes-2 class=tabs-scala-version %} {% tab 'Scala 2' for=generic-classes-2 %}
valstack=newStack[Int] stack.push(1) stack.push(2) println(stack.pop()) // prints 2 println(stack.pop()) // prints 1
{% endtab %} {% tab 'Scala 3' for=generic-classes-2 %}
valstack=Stack[Int] stack.push(1) stack.push(2) println(stack.pop()) // prints 2 println(stack.pop()) // prints 1
{% endtab %} {% endtabs %}
The instance stack
can only take Ints. However, if the type argument had subtypes, those could be passed in:
{% tabs generic-classes-3 class=tabs-scala-version %} {% tab 'Scala 2' for=generic-classes-3 %}
classFruitclassAppleextendsFruitclassBananaextendsFruitvalstack=newStack[Fruit] valapple=newApplevalbanana=newBanana stack.push(apple) stack.push(banana)
{% endtab %} {% tab 'Scala 3' for=generic-classes-3 %}
classFruitclassAppleextendsFruitclassBananaextendsFruitvalstack=Stack[Fruit] valapple=Apple() valbanana=Banana() stack.push(apple) stack.push(banana)
{% endtab %} {% endtabs %}
Class Apple
and Banana
both extend Fruit
so we can push instances apple
and banana
onto the stack of Fruit
.
Note: subtyping of generic types is invariant. This means that if we have a stack of characters of type Stack[Char]
then it cannot be used as an integer stack of type Stack[Int]
. This would be unsound because it would enable us to enter true integers into the character stack. To conclude, Stack[A]
is only a subtype of Stack[B]
if and only if B = A
. Since this can be quite restrictive, Scala offers a type parameter annotation mechanism to control the subtyping behavior of generic types.