06. Objects (enums) - RobertMakyla/scalaWiki GitHub Wiki

Singleton

Scala has no static methods, but it has Singletons:

object Account {
    private var lastNumber = 0
    def newUniqueNumber() = { lastNumber += 1; lastNumber }
}

Note: newUniqueNumber is a function with () because it changes the state.

Object's constructor is called when object is used for the first time :

Account.newUniqueNumber()

Object may contain everything that a class contains.

Object may extend class or trait. But it can never have constructor parameters !!!

Account.newUniqueNumber()        // 1
Account.newUniqueNumber          // 2 - ok without () cause it's parameterless
Account.newUniqueNumber()        // 3

Class may have Companion Object (equivalent of java's instance methods and static methods)

{
    class Account {
        val id = Account.newId()
    }
    object Account {            // companion object
        private var lastId = 0
        private def newId() = { lastId += 1; lastId }
    }
}

Note: class and companion object: (interview)

  • have the same names
  • see each other private properties / private methods - but not object-private things
  • companion object see private constructor of the class
  • are not in scope of each other (class cannot call directly companion's methods)
  • must be located in the same source file

Objects extending classes or Traits

Extending object gets all the features of class/trait, e.g. :

abstract class UndoableAction(val description: String) {
    def undo(): Unit   // abstract method in abstract class. There is no '=' or '{}'
    def redo(): Unit
}

object DoNothingAction extends UndoableAction("Do nothing") {
    def undo() {}      // 'override' not needed cause super method is abstract
    def redo() {}
}

The apply method (interview)

It is common for objects to have apply methods. Typically it returns instance of companion class.

Thanks to apply() method, while instantiating a class we don't need to use new

Array(10)      
// this calls apply(10) yielding Array[Int] with a single integer element : 10

new Array(10)  
// this invokes this(10) constructor. Result is Array[Nothing] with 10 null elements

e.g. :

{
    class Account(val id: Int, var balance: Int) {
        println("Account created: Id="+id+", balance="+balance)
        def deposit(amount:Int) {balance += amount}
    }

    object Account {
        private var lastId = 0;
        private def newId() = {lastId += 1 ; lastId}

        def apply(initBalance: Int) = new Account(newId(), initBalance)
    }

    val a = Account(100)  // id = 1
    val b = Account(50)   // id = 2
    val c = Account(130)  // id = 3
}

Application objects

Instead of starting application with main(args:Array[String]) : Unit

object Hello {
    def main(args: Array[String]) {
        println("Hello, World!")
    }
}

I can extend App (which even has access to args)

object Hello extends App {
    if (args.length > 0) println("Hello, " + args(0))
    else println("Hello, World!")
}

Enumerations

object TrafficLightColor extends Enumeration {
    val Red, Yellow, Green = Value     // val Red = Value ; val Yellow = Value; etc..
}

It's the same as:

object TrafficLightColor extends Enumeration {
    val Red    = Value(0, "Red")
    val Yellow = Value(1, "Yellow")
    val Green  = Value(2, "Green")
}

class Value can be provided with custom id/name, or both:

object TrafficLightColor extends Enumeration {
    val Red = Value("Stop")  // same as Value(0, "Stop") cause first ID is always 0
    val Yellow = Value(10)   // name "Yellow", ID 10
    val Green = Value("Go")  // name "Go", ID 11, cause it's  10 + 1
}

Now, I can use it :

import TrafficLightColor._
def doWhat(color: TrafficLightColor) = {
    if (color == Red) "stop"
    else if (color == Yellow) "hurry up"
    else "go"
}

Iterating through enums:

for (c <- TrafficLightColor.values) println(c.id + ": " + c)

Getting ID for enum:

TrafficLightColor.Red.id
TrafficLightColor.withName("Red").id
TrafficLightColor withName("Red") id
TrafficLightColor withName {"Red"} id

Exercises

Define a Point class with a companion object so that you can construct Point instances as Point(3, 4), without using new.

{
    class Point private (val x: Int, val y: Int) {
        print(s"Creating point ($x, $y)")
    }

    object Point {
      def apply(x: Int, y: Int) = new Point(x, y)
    }

    val p = Point (1, 2)
}