{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "

Previous

\n", "

Next

\n", "

Tour of Scala

\n", "
\n", "\n", "# Generic Classes\n", "Generic classes are classes which take a type as a parameter. They are particularly useful for collection classes.\n", "\n", "## Defining a generic class\n", "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." ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mclass\u001b[39m \u001b[36mStack\u001b[39m" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Stack[A] {\n", " private var elements: List[A] = Nil\n", " def push(x: A) { elements = x :: elements }\n", " def peek: A = elements.head\n", " def pop(): A = {\n", " val currentTop = peek\n", " elements = elements.tail\n", " currentTop\n", " }\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "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`).\n", "\n", "## Usage\n", "\n", "To use a generic class, put the type in the square brackets in place of `A`." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```tut // TODO why doesn't it work in almond?\n", "val stack = new Stack[Int]\n", "stack.push(1)\n", "stack.push(2)\n", "println(stack.pop) // prints 2\n", "println(stack.pop) // prints 1\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The instance `stack` can only take Ints. However, if the type argument had subtypes, those could be passed in:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```tut // TODO why doesn't it work in almond?\n", "class Fruit\n", "class Apple extends Fruit\n", "class Banana extends Fruit\n", "\n", "val stack = new Stack[Fruit]\n", "val apple = new Apple\n", "val banana = new Banana\n", "\n", "stack.push(apple)\n", "stack.push(banana)\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Class `Apple` and `Banana` both extend `Fruit` so we can push instances `apple` and `banana` onto the stack of `Fruit`.\n", "\n", "_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](variances.ipynb) to control the subtyping behavior of generic types._\n", "

Previous

\n", "

Next

\n", "

Tour of Scala

\n", "
" ] } ], "metadata": { "kernelspec": { "display_name": "Scala (2.13)", "language": "scala", "name": "scala213" }, "language_info": { "codemirror_mode": "text/x-scala", "file_extension": ".scala", "mimetype": "text/x-scala", "name": "scala", "nbconvert_exporter": "script", "version": "2.13.1" } }, "nbformat": 4, "nbformat_minor": 4 }