Scala is a pure object-oriented language. Conceptually, every value is an object and every operation is a method call. You can follow along with the below examples by creating an additional file in the editor that has the same name as the class. For example, to create a class Recipe
add a Recipe.scala
file in the src/main/scala
directory and then add the following definition in the file:
class Recipe(calories: Int) {
println(s"Created recipe with ${calories} calories")
var cookTime: Int = 0
}
This creates a Scala class called Recipe
with a constructor that takes calories as a parameter. Unlike Java, the constructor arguments are included in the class definition. Then any expressions in the body of the class are considered part of the “primary constructor”, such as the println
. If you need alternative ways to create instances of the class, you can define secondary constructors with different arguments, but that is not covered here.
In addition to constructor parameters, a class has values and variables, such as the variable cookTime
in the previous example. Values are declared using the val
keyword and variables are declared using the var
keyword. By default the constructor parameters for a class are only visible inside the class itself and only values and variables are visible outside of the class. To understand the differences back in the Hello.scala
source file, try creating an instance of Recipe
by invoking the primary constructor and then try printing out the values or variables from the class:
val r = new Recipe(100) // outputs Created recipe with 100 calories
println(r.cookTime) // outputs 0
r.cookTime = 2
println(r.cookTime) // outputs 2
// This will produce an error - value calories is not a member of recipe
println(r.calories)
To promote the constructor parameters use the prefix val
or var
depending on whether they are supposed to be immutable or mutable. For example, change the Recipe class definition to:
class Recipe(val calories: Int) {
Then try printing out the calories again in the Hello
object. Now the compiler should be happy.
Method definitions in Scala start with the keyword def
followed by the name of the method and its parameters. The return type of the method can be inferred, just like with variables, when not explicitly declared. The following defines a method estimatedEffort
that calculates the estimated effort for the recipe based on the number of servings and returns per Int
. The return value is the last line of the method.
// Declares method that returns an Int - Int return is optional.
def estimatedEffort(servings:Int): Int = {
println("estimating the effort...")
servings * cookTime * calories
}
We can also create subclasses by extending abstract or non-final classes just like any object-oriented language. The one difference is the constructor parameters of the class being extended need to be passed in as well.
class Food(calories: Int)
class Salad(val lettuceCalories: Int, val dressingCalories: Int)
extends Food(lettuceCalories + dressingCalories)
When extending a class we can also override members of parent classes.
// Example of Overriding Methods
class Menu(items: List[Food]) {
def numberOfMenuItems() = items.size
}
// Dinners only consists of Salads
class Dinner(items: List[Salad]) extends Menu(items) {
// Overriding def as val
override def numberOfMenuItems = 2 * items.size
}
val s1 = new Salad(5, 5)
val s2 = new Salad(15, 15)
val dinner = new Dinner(List(s1, s2))
// prints 4
println(s"Number Of Menu Items = ${dinner.numberOfMenuItems}")
Scala does not have a static keyword like Java to mark a definition as a static (singleton) instance. Instead it uses the object
keyword to declare a singleton object. All the members defined inside the object are treated as singleton members and can be accessed without creating an explicit instance. The astute reader might have noticed that the definition in Hello.scala
is declared as the object Hello
. That is because a single instance of Hello
object body becomes the “main method” because it extends App
, which is a trait. Traits are covered later.
A common use of the singleton object is for declaring constants. For example, add the following line above the Hello main
method:
val watermelonCalories = 98
Then access the constant with the following line inside the Hello
object:
println(watermelonCalories)
Objects are commonly used to hold factory methods for creating classes. This pattern is so common that Scala declares a special method definition for this called apply
. The apply
method can be thought of like a default factory method that allows the object to be called like a method. For example, inside the Recipe.scala
file, add an object above the class definition:
object Recipe {
// Assume the client was only guessing the calories and
// double just in case
def apply(calories: Int) = new Recipe(calories * 2)
}
Then back in the Hello
object, change the creation of Recipe to the following:
// This call refers to the Recipe object and is the same as calling Recipe.apply(100)
val r = Recipe(100) // apply method is called by default
println(r.calories) // outputs 200
What is interesting about the use of the Recipe object here is that no method had to be specified—apply was called by default.
An object is considered a companion object when it is declared in the same file as the class and shares the name and the package. Companion objects are used to hold the static definitions related to class but do not have any special relationship to a class.
{{ parent.title || parent.header.title}}
{{ parent.tldr }}
{{ parent.linkDescription }}
{{ parent.urlSource.name }}