Mastering Scala Basics: Objects & Classes

Just like other programming languages, we use class to create objects that have similar methods and fields. In addition, class in Scala defines a type and the objects created from a class share the same type.

class Employee {
    val name = "John"
    val id: Int = 10
    def details = name + ":" + id
}

We can create a new Employee using new keyword.

val john = new Employee
// val John: Employee = Employee@53940e2f

This @xxxx is a unique identifier for that particular object Employee@53940e2f. new always creates a distinct object of the same type.

val otherJohn = new Employee
// val otherJohn: Employee = Employee@5a090f62

What this indicates is - we can write a method that takes Employee as a parameter

object empl {
    def greet(e: Employee) = "Greetings " + e.name
}

empl.greet(john)
// val res1: String = Greetings John

empl.greet(otherJohn)
// val res2: String = Greetings John

Now, let’s use constructors to pass new parameters to new objects as we create them.

// val is optional
class Employee(val name: String, val id: Int, val location: String)
// class Employee

new Employee("Jill", 10, "NY").location
// val res3: String = NY

Primary constructor parameters with val and var are public. However, parameters without val and var are private, visible only within the class.

class Employee(name: String, id: Int, location: String)

val emp = new Employee("Dre", 10, "NY")
emp.id // does not compile

Scala Type Hierarchy

Type Hierarchy in Scala
Type Hierarchy in Scala

Any is the supertype of all types, also called the top type. It defines certain universal methods such as equals, hashCode, and toString. Any has two direct subclasses: AnyVal and AnyRef.

AnyVal represents value types. There are nine predefined value types and they are non-nullable: Double, Float, Long, Int, Short, Byte, Char, Unit, and Boolean.

AnyRef represents reference types. All non-value types are defined as reference types. Every user-defined type in Scala is a subtype of AnyRef.

val list: List[Any] = List(
    "a string",
    10,
    () => 10.2,
    true
)
// val list: List[Any] = List(a string, 10, $Lambda$1104/0x00000008006af440@2842c098, true)

Nothing is a subtype of all types, also called the bottom type. A common use is to signal non-termination such as a thrown exception, program exit, or an infinite loop (i.e., it is the type of an expression which does not evaluate to a value, or a method that does not return normally).

Null is a subtype of all reference types (i.e. any subtype of AnyRef). It has a single value identified by the keyword literal null. Null is provided mostly for interoperability with other JVM languages and should almost never be used in Scala code.

Objects as functions

In Scala, the object keyword creates a Singleton object. Objects have several uses:

  • They are used to create collections of utility methods.
  • A companion object is an object that has the same name as the class it shares a file with. In this situation, that class is also called a companion class.
  • They’re used to implement traits to create modules.

Scala provides features for creating objects that behave like computations, called functions and are the basis of functional programming.

An object can be called like a function if it has an apply method.

class Add(amount: Int) {
    def apply(in: Int): Int = in + amount
}
// class Add

val add = new Add(3)
// val add: Add = Add@61d527ac

add.apply(2)
// val res4: Int = 5

add(2)   // shorthand
//val res5: Int = 5

We use companion objects in Scala to create method that belongs to a class but is independent of any particular object. In Java, we use static method for that. In addition, companion objects are singletons with its own type.

class Add(val amt: Int)

object Add {
    def apply(in: Int, amt: Int): Add = new Add(in + amt)
}

Add(10, 2)
// val res0: Add = Add@3e52ed5d

Add(10, 2).amt
// val res1: Int = 12

Case classes are useful shorthand to create class, companion objects including some exceptional features. When we define case class, Scala automatically generates a class and companion object:

case class Employee(name: String, val location: String) {
    def details = name + "lives in " + location
}

val Kev = new Employee("Kev", "NY") // class
// val Kev: Employee = Employee(Kev,NY)

Kev // companion object
// val res2: Employee = Employee(Kev,NY)

Features of case class:

  1. A default toString method that prints a sensible representation
Kev
// val res2: Employee = Employee(Kev,NY)
  1. Sensible equals and hashCode methods that operate on field values in the object
new Employee("Kev", "NY").equals(new Employee("Kev", "NY"))
// val res3: Boolean = true

new Employee("Kev", "NY") == new Employee("Kev", "NY")
// val res4: Boolean = true
  1. A copy method that creates a new object with the same field values as the current one
Kev.copy()
// val res5: Employee = Employee(Kev,NY)

Define case object for case classes that has no constructor arguments.

case object Person {
    val name: String = "Jake Doe"
}

Case classes allow a new form of interaction, called pattern matching. Pattern matching allows us to take apart a class, and evaluate different expressions depending on what the case class contains.

They syntax of pattern matching

expr match {
    case pattern1 => expr1
    case pattern2 => expr2
}

A pattern can be any of:

  1. a name, binding any value to that name
  2. an underscore, matching any value and ignoring it
  3. a literal, matching the value the literal denotes
  4. a constructor-style pattern for a case class.
case class Employee(name: String, val location: String)

object TestEmployee {
    def getEmployee(e: Employee): String = 
        e match {
            case Employee("Jake", "CA") => "It's Jake from CA"
            case Employee("Jake", _) => "It's Jake"
            case Employee(name, loc) => "It's " + name + " " + "from " + loc
        }
}

TestEmployee.getEmployee(Employee("Jake", "NY"))
// val res6: String = It's Jake

TestEmployee.getEmployee(Employee("Jake", "CA"))
// val res7: String = It's Jake from CA

TestEmployee.getEmployee(Employee("Jack", "PA"))
// val res9: String = It's Jack from PA

References used are Scala Doc and Essential Scala