Skip to content

Latest commit

 

History

History
217 lines (156 loc) · 6.9 KB

singleton-objects.md

File metadata and controls

217 lines (156 loc) · 6.9 KB
layouttitlepartofnumnext-pageprevious-pageredirect_fromprerequisite-knowledge
tour
Singleton Objects
scala-tour
15
regular-expression-patterns
pattern-matching
/tutorials/tour/singleton-objects.html
classes, methods, private-methods, packages, option

An object is a class that has exactly one instance. It is created lazily when it is referenced, like a lazy val.

As a top-level value, an object is a singleton.

As a member of an enclosing class or as a local value, it behaves exactly like a lazy val.

Defining a singleton object

An object is a value. The definition of an object looks like a class, but uses the keyword object:

{% tabs object-definition-box %}

{% tab 'Scala 2 and 3' for=object-definition-box %}

objectBox

{% endtab %}

{% endtabs %}

Here's an example of an object with a method: {% tabs singleton-logger-example class=tabs-scala-version %}

{% tab 'Scala 2' for=singleton-logger-example %}

packageloggingobjectLogger { definfo(message: String):Unit= println(s"INFO: $message") }

{% endtab %}

{% tab 'Scala 3' for=singleton-logger-example %}

packageloggingobjectLogger:definfo(message: String):Unit= println(s"INFO: $message")

{% endtab %}

{% endtabs %}

The method info can be imported from anywhere in the program. Creating utility methods like this is a common use case for singleton objects.

Let's see how to use info in another package: {% tabs singleton-usage-example class=tabs-scala-version %}

{% tab 'Scala 2' for=singleton-usage-example %}

importlogging.Logger.infoclassProject(name: String, daysToComplete: Int) classTest { valproject1=newProject("TPS Reports", 1) valproject2=newProject("Website redesign", 5) info("Created projects") // Prints "INFO: Created projects" }

{% endtab %}

{% tab 'Scala 3' for=singleton-usage-example %}

importlogging.Logger.infoclassProject(name: String, daysToComplete: Int) classTest:valproject1=Project("TPS Reports", 1) valproject2=Project("Website redesign", 5) info("Created projects") // Prints "INFO: Created projects"

{% endtab %}

{% endtabs %}

The info method is visible because of the import statement, import logging.Logger.info.

Imports require a "stable path" to the imported symbol, and an object is a stable path.

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.

Companion objects

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. {% tabs companion-object-circle class=tabs-scala-version %}

{% tab 'Scala 2' for=companion-object-circle %}

importscala.math.{Pi, pow} caseclassCircle(radius: Double) { importCircle._defarea:Double= calculateArea(radius) } objectCircle { privatedefcalculateArea(radius: Double):Double=Pi* pow(radius, 2.0) } valcircle1=Circle(5.0) circle1.area

{% endtab %}

{% tab 'Scala 3' for=companion-object-circle %}

importscala.math.{Pi, pow} caseclassCircle(radius: Double):importCircle.*defarea:Double= calculateArea(radius) objectCircle:privatedefcalculateArea(radius: Double):Double=Pi* pow(radius, 2.0) valcircle1=Circle(5.0) circle1.area

{% endtab %}

{% endtabs %}

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.

The companion object can also contain factory methods: {% tabs companion-object-email class=tabs-scala-version %}

{% tab 'Scala 2' for=companion-object-email %}

classEmail(valusername:String, valdomainName:String) objectEmail { deffromString(emailString: String):Option[Email] = { emailString.split('@') match { caseArray(a, b) =>Some(newEmail(a, b)) case _ =>None } } } valscalaCenterEmail=Email.fromString("scala.center@epfl.ch") scalaCenterEmail match { caseSome(email) => println( s"""Registered an email |Username: ${email.username}  |Domain name: ${email.domainName} """.stripMargin) caseNone=> println("Error: could not parse email") }

{% endtab %}

{% tab 'Scala 3' for=companion-object-email %}

classEmail(valusername:String, valdomainName:String) objectEmail:deffromString(emailString: String):Option[Email] = emailString.split('@') matchcaseArray(a, b) =>Some(Email(a, b)) case _ =>NonevalscalaCenterEmail=Email.fromString("scala.center@epfl.ch") scalaCenterEmail matchcaseSome(email) => println( s"""Registered an email |Username: ${email.username}  |Domain name: ${email.domainName} """.stripMargin) caseNone=> println("Error: could not parse email")

{% endtab %}

{% endtabs %}

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.

A note about Option, Some, and None in the code above:

  • Option is a data type which allows for optionality. It has two cases: Some and None
    • Some above represents a match: the emailString, when split by a @, returns an array with two components. This allows creation of a valid instance of class Email.
    • None above represents no match: the emailString, when split by a @, did not return an array with two components. It could not allow creation of a valid instance of class Email.
  • The Option return type can then be used in a match/case:
    • For a Some result, the match knows the returned value is an instance of Email, so it can access the inner username and domainName.
    • For a None result, the match knows the returned value is not an instance of Email, so it prints an appropriate error message.

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.

Notes for Java programmers

static members in Java are modeled as ordinary members of a companion object in Scala.

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.

More resources

  • Learn more about Companion objects in the Scala Book
close