Inheritance model
Scala features a lot of object-oriented functionality. This means it supports inheritance concepts that are core to the object-oriented programming. Moreover, since Scala compiles to the Java Virtual Machine, it is essential that it supports the same model as Java for Java interoperability reasons.
Classes
Classes in Scala have similar semantics to their Java counterparts. They are defined as follows:
scala> :paste // Entering paste mode (ctrl-D to finish) class Dummy(constructorArgument: String) { var variable: Int = 0 val value: String = constructorArgument * 2 def method(x: Int): String = s"You gave me $x" } // Exiting paste mode, now interpreting. defined class Dummy scala> new Dummy("Foo") res15: Dummy = Dummy@1a2f7e20 scala> res15.variable res16: Int = 0 scala> res15.value res17: String = FooFoo scala> res15.method(2) res18: String = You gave me 2
Also, it is possible to define so-called case classes in Scala. These classes are used to represent product types, that is, several types bound together in one datatype. For example, you can define a case class for the User
domain object as follows:
scala> case class User(id: Int, name: String, passwordHash: String) defined class User
As follows from their name, case classes are primarily used for pattern matching. When you are defining a case class, the compiler automatically generates extractors for this class, so that it can be used in pattern matching, as follows:
scala> val user = User(1, "dummyuser123", "d8578edf8458ce06fbc5bb76a58c5ca4") user: User = User(1,dummyuser123,d8578edf8458ce06fbc5bb76a58c5ca4) scala> user match { | case User(id, name, hash) => println(s"The user $name has id $id and password hash $hash") | } The user dummyuser123 has id 1 and password hash d8578edf8458ce06fbc5bb76a58c5ca4
Also, the compiler generates convenient toString
, equals
, and hashCode
methods for case classes:
scala> user.toString res20: String = User(1,dummyuser123,d8578edf8458ce06fbc5bb76a58c5ca4) scala> val user2 = User(user.id, user.name, user.passwordHash) user2: User = User(1,dummyuser123,d8578edf8458ce06fbc5bb76a58c5ca4) scala> user.equals(user2) res21: Boolean = true scala> user.hashCode res22: Int = -363163489 scala> user2.hashCode res23: Int = -363163489
Case classes are especially useful when modeling your domain.
Traits
The concept of an object-oriented interface is encapsulated in a trait in Scala. Similarly to an interface, a trait can have abstract members. However, unlike Java interfaces, traits may also have concrete members. These are injected into the implementing classes:
scala> :paste // Entering paste mode (ctrl-D to finish) trait Foo { def saySomething = println("I am inherited from Foo") } // Exiting paste mode, now interpreting. defined trait Foo
Just like in Java, Scala classes can implement more than one trait. However, since traits in Scala can have concrete members, a new inheritance model that allows for that is required.
In Scala, a so-called linearization model is implemented. This means that whenever a class is inherited from multiple traits, they are organized into a clear sequence, which determines the priority of inheritance. For example, consider the following inheritance case:
scala> :paste // Entering paste mode (ctrl-D to finish) trait Foo { def saySomething = println("I am inherited from Foo") } trait Bar { def saySomething = println("I am inherited from Bar") } class Dummy extends Foo with Bar { override def saySomething = super.saySomething } // Exiting paste mode, now interpreting. defined trait Foo defined trait Bar defined class Dummy scala> new Dummy().saySomething I am inherited from Bar
In this case, the Bar
trait will get a priority over the Foo
trait. This allows you to inherit from multiple traits and be aware of the precise sequence in which they will be applied.
Singleton objects
In Scala, it is impossible to make a class have static members. However, the concept of a static member is present in Java. Since Scala compiles to JVM, it needs a way to model this concept from Java. In Scala, a concept of a singleton object is used to model static members:
scala> :paste // Entering paste mode (ctrl-D to finish) object Foo { def say = println("I am Foo") } // Exiting paste mode, now interpreting. defined object Foo scala> Foo.say I am Foo
In the preceding code, we can call the members of the singleton object without instantiating it or doing anything else with it, directly by its name. This is because it is a standalone fully fledged object that is constructed by our object
statement. It exists in a single instance for the entire JVM.
The concept of a singleton object can be leveraged to model static members from Java. In Scala, there is a concept of a so-called companion object of a trait or a class. For any trait or class, if you define an object with the same name as the entity in question, it is considered a companion object to it. All of the static members of this class are defined as members of this singleton object. This allows you a clear separation between object and types. No longer can you call a member of a class without instantiating it.