A generic class is parameterized by a type variable. We can think of it as a function that maps types into classes.
import
scala.collection.mutable.ArrayBuffer
class Stack[T] ( val capacity: Int = 100) {
private
val elems: ArrayBuffer[T]
= new ArrayBuffer[T](capacity)
private
var sp = 0
def
push(elem: T) {
elems += elem
sp += 1
}
def
pop() { if (sp > 0) sp -= 1 }
def
top: Option[T] = if (sp
> 0) Some(elems(sp - 1)) else None
}
Notes
· We don't include elems and sp in the constructor's parameter list. The user doesn't get to specify these. Only the capacity can be specified and this has a default value, so its specification is optional.
· The stack pointer (sp) is never negative.
· The empty pair of parentheses after pop is not required, but helps distinguish parameterless procedures from parameterless functions.
· What should we do if a user asks for the top of an empty stack? The choices: throw and exception or return an option.
scala> val
stack1 = Stack[String]
stack1: Stack[String] = Stack@680d4a6a
scala> stack1.push("one")
scala> stack1.push("two")
scala> stack1.top
res26: Option[String] = Some(two)
scala> stack1.pop
scala> stack1.top
res28: Option[String] = Some(one)
scala> stack1.pop
scala> stack1.top
res30: Option[String] = None
scala> stack1.push(10)
<console>:16: error: type mismatch;
found
: Int(10)
required: String
stack1.push(10)
Notes
· Pushing an integer onto a string stack is a type mismatch.
We can constrain a type parameter to be a subtype of some other type. For example, if we only wanted our stacks to contain values, and not references, then we could write:
class Stack[T <: AnyVal] ( val capacity: Int = 100) {...}
And now:
scala> val stack
= new Stack[String]
<console>:14: error: type arguments [String] do not conform to method
<init>$default$1's type parameter bounds [T <: AnyVal]
Error occurred in an application involving default arguments.
val stack = new Stack[String]