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

Previous

\n", "

Next

\n", "

Tour of Scala

\n", "
\n", "\n", "# Singleton Objects\n", "An object is a class that has exactly one instance. It is created lazily when it is referenced, like a lazy val.\n", "\n", "As a top-level value, an object is a singleton.\n", "\n", "As a member of an enclosing class or as a local value, it behaves exactly like a lazy val.\n", "# Defining a singleton object\n", "An object is a value. The definition of an object looks like a class, but uses the keyword `object`:" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "defined \u001b[32mobject\u001b[39m \u001b[36mBox\u001b[39m" ] }, "execution_count": 1, "metadata": {}, "output_type": "execute_result" } ], "source": [ "object Box" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's an example of an object with a method:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```scala\n", "package logging\n", "\n", "object Logger {\n", " def info(message: String): Unit = println(s\"INFO: $message\")\n", "}\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The method `info` can be imported from anywhere in the program. Creating utility methods like this is a common use case for singleton objects.\n", "\n", "Let's see how to use `info` in another package:" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "```scala\n", "import logging.Logger.info\n", "\n", "class Project(name: String, daysToComplete: Int)\n", "\n", "class Test {\n", " val project1 = new Project(\"TPS Reports\", 1)\n", " val project2 = new Project(\"Website redesign\", 5)\n", " info(\"Created projects\") // Prints \"INFO: Created projects\"\n", "}\n", "```\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `info` method is visible because of the import statement, `import logging.Logger.info`.\n", "\n", "Imports require a \"stable path\" to the imported symbol, and an object is a stable path.\n", "\n", "Note: If an `object` is not top-level but is nested in another class or object, then the object is \"path-dependent\" like any other member. This means that given two kinds of beverages, `class Milk` and `class OrangeJuice`, a class member `object NutritionInfo` \"depends\" on the enclosing instance, either milk or orange juice. `milk.NutritionInfo` is entirely distinct from `oj.NutritionInfo`.\n", "\n", "## Companion objects\n", "\n", "An object with the same name as a class is called a _companion object_. Conversely, the class is the object's companion class. A companion class or object can access the private members of its companion. Use a companion object for methods and values which are not specific to instances of the companion class." ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "data": { "text/plain": [ "\u001b[32mimport \u001b[39m\u001b[36mscala.math._\n", "\n", "\u001b[39m\n", "defined \u001b[32mclass\u001b[39m \u001b[36mCircle\u001b[39m\n", "defined \u001b[32mobject\u001b[39m \u001b[36mCircle\u001b[39m\n", "\u001b[36mcircle1\u001b[39m: \u001b[32mCircle\u001b[39m = \u001b[33mCircle\u001b[39m(\u001b[32m5.0\u001b[39m)\n", "\u001b[36mres1_4\u001b[39m: \u001b[32mDouble\u001b[39m = \u001b[32m78.53981633974483\u001b[39m" ] }, "execution_count": 2, "metadata": {}, "output_type": "execute_result" } ], "source": [ "import scala.math._\n", "\n", "case class Circle(radius: Double) {\n", " import Circle._\n", " def area: Double = calculateArea(radius)\n", "}\n", "\n", "object Circle {\n", " private def calculateArea(radius: Double): Double = Pi * pow(radius, 2.0)\n", "}\n", "\n", "val circle1 = new Circle(5.0)\n", "\n", "circle1.area" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `class Circle` has a member `area` which is specific to each instance, and the singleton `object Circle` has a method `calculateArea` which is available to every instance.\n", "\n", "The companion object can also contain factory methods:" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "attributes": { "classes": [ "tut" ], "id": "" } }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Registered an email\n", " |Username: scala.center\n", " |Domain name: epfl.ch\n", " \n" ] }, { "data": { "text/plain": [ "defined \u001b[32mclass\u001b[39m \u001b[36mEmail\u001b[39m\n", "defined \u001b[32mobject\u001b[39m \u001b[36mEmail\u001b[39m\n", "\u001b[36mscalaCenterEmail\u001b[39m: \u001b[32mOption\u001b[39m[\u001b[32mEmail\u001b[39m] = \u001b[33mSome\u001b[39m(\n", " ammonite.$sess.cmd2$Helper$Email@432ba47b\n", ")" ] }, "execution_count": 3, "metadata": {}, "output_type": "execute_result" } ], "source": [ "class Email(val username: String, val domainName: String)\n", "\n", "object Email {\n", " def fromString(emailString: String): Option[Email] = {\n", " emailString.split('@') match {\n", " case Array(a, b) => Some(new Email(a, b))\n", " case _ => None\n", " }\n", " }\n", "}\n", "\n", "val scalaCenterEmail = Email.fromString(\"scala.center@epfl.ch\")\n", "scalaCenterEmail match {\n", " case Some(email) => println(\n", " s\"\"\"Registered an email\n", " |Username: ${email.username}\n", " |Domain name: ${email.domainName}\n", " \"\"\")\n", " case None => println(\"Error: could not parse email\")\n", "}" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `object Email` contains a factory `fromString` which creates an `Email` instance from a String. We return it as an `Option[Email]` in case of parsing errors.\n", "\n", "Note: If a class or object has a companion, both must be defined in the same file. To define companions in the REPL, either define them on the same line or enter `:paste` mode.\n", "\n", "## Notes for Java programmers ##\n", "\n", "`static` members in Java are modeled as ordinary members of a companion object in Scala.\n", "\n", "When using a companion object from Java code, the members will be defined in a companion class with a `static` modifier. This is called _static forwarding_. It occurs even if you haven't defined a companion class yourself.\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 }