{{announcement.body}}
{{announcement.title}}

Scala at Light Speed, Part 2: Object Orientation

DZone 's Guide to

Scala at Light Speed, Part 2: Object Orientation

Check out this post to learn more about object-orientation in Scala.

· Java Zone ·
Free Resource

Check out this post to learn more about object-orientation in Scala.

This article series is for busy programmers who want to learn Scala fast, in 2 hours or less. The articles are the written version of Rock the JVM's Scala at Light Speed mini-course, which you can find for free on YouTube or on the Rock the JVM website in video form.

You may also like: Scala at Light Speed, Part 1: The Essentials

This is the second article of the series, which will focus on Scala as an object-oriented language. You can watch it in video form here or in the embedded video below.

In the previous part, we learned:

  • How to set up a Scala project in IntelliJ IDEA
  • Values, expressions, and types
  • Functions and recursion
  • The Unit type

Classes

Nothing too fancy. Scala has the same notion of a class as you likely saw in other languages.

Scala




x


 
1
  class Animal {
2
    // define fields
3
    val age: Int = 0
4
    // define methods
5
    def eat() = println("I'm eating")
6
  }
7
 
          
8
  val anAnimal = new Animal
9
  anAnimal.eat()



Fields are defined with val, methods with def, and they work as we discussed in Part 1. Fields and methods are accessed with the dot operator much like other languages.

Scala has single class inheritance (much like Java), as shown below:

Scala




xxxxxxxxxx
1


 
1
  // inheritance
2
  class Dog(name: String) extends Animal
3
  val aDog = new Dog("Lassie")



Classes in Scala can take arguments — those are the constructor arguments. When you define a class, you also define its constructor signature. In this case, the Dog constructor takes a single String argument.

Beware that constructor argument are not fields — the code below will trigger an error.

Scala




xxxxxxxxxx
1


 
1
  aDog.name



In order to save a constructor argument as a field, put a val  before the argument name:

Scala




xxxxxxxxxx
1


 
1
  class Dog(val name: String) extends Animal



Notice that nothing is mutable (we have the notion of a variable in Scala, but it's heavily discouraged). In general, we work with immutable data structures: any intended modification to an existing instance should return a new (modified) instance.

Inheritance, Traits, and Method Notations

Scala has the same subtype polymorphism you may have seen in other statically typed languages:

Scala




xxxxxxxxxx
1


 
1
  val aDeclaredAnimal: Animal = new Dog("Hachi") // off-topic: Hachi is an amazing movie
2
  aDeclaredAnimal.eat() // the most derived method will be called at runtime



Meaning, we declared a value to be an Animal, but at runtime, it acts like a Dog. A derived class can, of course, override their superclass methods.

Scala also has the notion of abstract classes, where you may leave fields/methods without implementation:

Scala




xxxxxxxxxx
1


 
1
  // abstract class
2
  abstract class WalkingAnimal {
3
    val hasLegs = true // by default public, can restrict by adding protected or private
4
    def walk(): Unit
5
  }



As the comment above mentions, all fields/methods are by default public unless we restrict their visibility with the  private and protected modifiers. They work exactly as in Java: private restricts access to the class only, protected restricts access to the class and all derived classes.

We also have the notion of an "interface," a type with everything being abstract — this is a trait:

Scala




xxxxxxxxxx
1


 
1
  // "interface" = ultimate abstract type
2
  trait Carnivore {
3
    def eat(animal: Animal): Unit
4
  }



(Although, we can also implement fields/methods in traits; long story)

Scala has single-class inheritance and multi-trait inheritance:

Scala




xxxxxxxxxx
1
10
9


1
  trait Philosopher {
2
    def ?!(thought: String): Unit // valid method name
3
  }
4
 
          
5
  // single-class inheritance, multi-trait "mixing"
6
  class Crocodile extends Animal with Carnivore with Philosopher {
7
    override def eat(animal: Animal): Unit = println("I am eating you, animal!")
8
    override def ?!(thought: String): Unit = println(s"I was thinking: $thought")
9
  }



First, notice that Crocodile derives from Animal and two other traits.

Second, notice that the Philosopher has a method called ?!Scala is very permissive with method naming, where method names can start with non-alphanumeric characters. Names like  ::: ,  ?# ,  --> ,  ~> are all valid, and they're used in practice. Examples include :: in working with lists, ?  and ! in working with Akka actors and ~> in working with Akka Streams, to name a few.

Third, watch below.

Scala




xxxxxxxxxx
1


 
1
  val aCroc = new Crocodile
2
  aCroc.eat(aDog)
3
  aCroc eat aDog // equivalent
4
  aCroc ?! "What if we could fly?"



The expressions in lines 3 and 4 above are called infix notation, which applies to methods with one argument. Instead of the instance.method(argument) call structure, we can use instance method argument. This makes Scala extremely expressive because we can make expressions look like natural language (a croc eats a dog, line 3) or like math operations (line 4).

Which brings us to another point. Even plain math operators like 1 + 2 are actually method calls:  1 + 2  is identical to  1.+(2): we apply the number 1's plus method on argument 2.

Finally, on inheritance, Scala has the notion of an anonymous class, much like Java:

Scala




xxxxxxxxxx
1


 
1
  // anonymous classes
2
  val dinosaur = new Carnivore {
3
    override def eat(animal: Animal): Unit = println("I can eat pretty much anything")
4
  }



What that essentially tells the compiler (pseudo-Scala, but not off by much):

Scala




xxxxxxxxxx
1


1
    class Carnivore_Anonymous_35728 extends Carnivore {
2
      override def eat(animal: Animal): Unit = println("I can eat pretty much anything")
3
    }
4
 
          
5
    val dinosaur = new Carnivore_Anonymous_35728



Objects and Companions

Scala has singletons as first-class elements with the keyword object:

Scala




x


 
1
  // singleton object
2
  object MySingleton { // the only instance of the MySingleton type
3
    val mySpecialValue = 53278
4
    def mySpecialMethod(): Int = 5327
5
  }



Once you define an object, you've defined both its type and the single instance of that type, and you can now use it as any other value.

You can also put a singleton — we're going to call them objects from now on — and a class (or a trait) with the same name in the same file: those are called companions. They can access each other's private fields.

Scala




xxxxxxxxxx
1


 
1
  class Animal { ... }
2
 
          
3
  object Animal { // companions - this is a companion object
4
    // companions can access each other's private fields/methods
5
    val canLiveIndefinitely = false
6
  }
7
 
          
8
  val animalsCanLiveForever = Animal.canLiveIndefinitely // "static" fields/methods



Notice how we access a field or a method without referring to an instance of Animal, but to the Animal singleton (which are always different things). That's analogous to "static" fields/methods you see in other languages.

Because all the code we write is part of either an instance of a class or a singleton, many people consider Scala even more object-oriented than Java!

The Apply Method

Methods in Scala named apply are special.

Scala




xxxxxxxxxx
1


 
1
  object MySingleton {
2
    def apply(x: Int): Int = x + 1
3
  }



Apply methods can be called in two ways on a singleton or class instance:

Scala




xxxxxxxxxx
1


 
1
  MySingleton.apply(65)
2
  MySingleton(65) // equivalent to MySingleton.apply(65)



One is the regular method call; the other is new. Apply allows instances to be "invoked" like functions. This will prove important in the next part when we talk about functional programming. You can add apply methods to any class, with any kind of arguments. Another example:

Scala




xxxxxxxxxx
1


 
1
class MyArrayList {
2
  def apply(index: Int): Int = ... // assume it gets the element at the given index 
3
}
4
 
          
5
val myArrayList = new MyArrayList
6
val thirdElement = myArrayList(2) // same as myArrayList.apply(2)



The Scala collections library uses this technique for many collections.

One trick: we can use apply in companion objects as factory methods:

Scala




xxxxxxxxxx
1


 
1
class Dog(name: String)
2
object Dog {
3
  def apply(name: String) = new Dog(name)
4
}
5
 
          
6
val myDog = Dog("Laika") // same as Dog.apply("Laika")



In this way, we can construct class instances without new. In the upcoming Scala 3, that will actually be done by default by the compiler.

Case Classes

In practice, we often need to work with lightweight data structures. We need them:

  • To have sensible equals/hashCodes so that they can be included in collections easily
  • To be serializable as we send them over the wire
  • To be easy to build
  • To be easy to deconstruct* (we'll show how later when we talk about pattern matching)

So that we don't have to write similar code every time, Scala has the notion of a case class for the above needs.

Scala




xxxxxxxxxx
1


 
1
  case class Person(name: String, age: Int)
2
  val bob = Person("Bob", 54)



When you define a case class, the compiler automatically generates:

  • Deep equals and hashCode
  • Serializing format
  • Acompanion object with an apply factory method as shown 30 seconds ago
  • Sensible deconstruction format* (talk later in pattern matching)
  • And more: copy methods, product iterators, curried constructor functions (no need to care about these for now)

Exceptions

Being based on the JVM, Scala has the same notion of exceptions like Java. The below code should serve as an example. This is similar to other languages that support exceptions, e.g. Java, C#, C++ (don't get me started with exceptions in C++).

Scala
xxxxxxxxxx
1
 
1
  try {
2
    // code that can throw
3
    val x: String = null
4
    x.length // BOOM!
5
  } catch { // in Java: catch(Exception e) {...}
6
    case e: Exception => "some faulty error message"
7
  } finally {
8
    // execute some code no matter what
9
  }


For Java developers: Scala doesn't have the notion of a checked exception, i.e. the type of which you need to declare or that you must catch.

A Bit of Generics

In Scala, we can attach type arguments to our classes/traits to reuse the code we write on multiple types. You've surely seen this in other statically-typed languages.

Scala
xxxxxxxxxx
1
 
1
  abstract class MyList[T] {
2
    def head: T
3
    def tail: MyList[T]
4
  }


In this case, we are using the generic type T to make the list mechanism applicable to ints, strings, persons, or other objects. The Scala standard library collections are generic. Examples:

Scala
xxxxxxxxxx
1
 
1
  val aList: List[Int] = List(1,2,3) // List.apply(1,2,3)
2
  val first = aList.head // int
3
  val rest = aList.tail
4
  val aStringList = List("hello", "Scala")
5
  val firstString = aStringList.head // string


The compiler still runs type inference and type checks so that we always operate on the correct types.

Stay Tuned

In the next article, we'll talk about Scala as a functional programming language.

Further Reading

Topics:
scala ,object-oriented programming ,functional programming

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}