Chapter 8. Classes
In Part 1 of this book you learned about Scala’s core types and how to group them into collections. Now it is time to build your own types with classes.
Classes are the core building block of object-oriented languages, a combination of data structures with functions (“methods”). A class defined with values and variables can be instantiated as many times as needed, each one initialized with its own input data. With inheritance classes can extend other classes, creating a hierarchy of subclasses and superclasses. Polymorphism makes it possible for these subclasses to stand in for their parent classes, while encapsulation provides privacy controls to manage the outward appearance of a class. If these terms are unfamiliar to you, I recommend reading up on general object-oriented programming methodology. Although we will cover the Scala object-oriented features that make use of these concepts, we won’t be spending time on learning the concepts themselves. Understanding them can help you to make the most of Scala’s object-oriented features and design expressive and reusable types.
We’ll start by defining the simplest possible class and instantiating it:
scala> class User defined class User scala> val u = new User u: User = User@7a8c8dcf scala> val isAnyRef = u.isInstanceOf[AnyRef] isAnyRef: Boolean = true
We now have our first class. When the REPL prints it out, you see the class name and a hexadecimal string. This is the JVM’s internal reference for that instance. If you create a new instance, you should see a different value printed out, because the second instance would have a different memory location and thus a different reference from the first instance.
The hexadecimal number printed after the name of our “User” clas may look a bit odd. The actual method printing this is the JVM’s java.lang.Object.toString
. The java.lang.Object
class is the root of all instances in the JVM, including Scala, and is essentially equivalent to the Scala root type Any
. By printing an instance, the REPL is invoking the instance’s toString
method, which it inherits from the root type. The actual parent type of our User
class is AnyRef
(see Table 2-4), the root of all instantiable types. Thus, invoking toString
on our User
class resulted in a call to its parent, AnyRef
, then to its parent, Any
, which is the same as java.lang.Object
and where the toString
method is located.
Let’s redesign our User
class and make it more useful. We’ll add a value and some methods that operate on the value. We’ll also override the default toString
method and provide a more informative version:
scala> class User { | val name: String = "Yubaba" | def greet: String = s"Hello from $name" | override def toString = s"User($name)" | } defined class User scala> val u = new User u: User = User(Yubaba) scala> println( u.greet ) Hello from Yubaba
We have values and methods, and a rockin’ toString
method that actually reveals the contents of this instance.
Let’s make this a bit more useful by converting the “name” field from a fixed value to a parameterized value. After all, no one really needs multiple instances of a User
class that all have the same name. In Scala, class parameters (if any) are specified after the name of the class, much like a function’s parameters follow the name of the function in its definition:
scala> class User(n: String) { | val name: String = n | def greet: String = s"Hello from $name" | override def toString = s"User($name)" | } defined class User scala> val u = new User("Zeniba") u: User = User(Zeniba) scala> println(u.greet) Hello from Zeniba
The class parameter “n” is used here to initialize the “name” value. However, it could not be used inside either of the methods. Class parameters are available for initializing fields (values and variables in a class) or for passing to functions, but once the class has been created the parameters aren’t available.
Instead of using a class parameter for intitialization purposes, we can instead declare one of the fields as a class parameter. By adding the keywords val
or var
before a class parameter, the class parameter then becomes a field in the class. Let’s try this by moving the “name” field to the class parameters:
scala> class User(val name: String) { | def greet: String = s"Hello from $name" | override def toString = s"User($name)" | } defined class User
Now that we have a short and useful class, let’s put it to use. Here’s an example of using this new class with lists:
scala> val users = List(new User("Shoto"), new User("Art3mis"), new User("Aesch")) users: List[User] = List(User(Shoto), User(Art3mis), User(Aesch)) scala> val sizes = users map (_.name.size) sizes: List[Int] = List(8, 7, 5) scala> val sorted = users sortBy (_.name) sorted: List[User] = List(User(Aesch), User(Art3mis), User(Shoto)) scala> val third = users find (_.name contains "3") third: Option[User] = Some(User(Art3mis)) scala> val greet = third map (_.greet) getOrElse "hi" greet: String = Hello from Art3mis
Did you notice that our new class is the type parameter to
List
? ThetoString
method that we’ve overridden ensures that theList
contents are cleanly displayed.Do you recall how operator notation (Methods and Operators) and placeholder syntax (Placeholder Syntax) work? Used together, they serve to make this line short and expressive.
On this line we have a Scala operation (
find
, which returns the first match by a predicate function, if available) and a Java operation (contains
, injava.lang.String
) being used with operation notation.Can you see why
String
is the correct result of a combination ofmap
andget
OrElse
against ourOption[String]
?
Lists and list operations aren’t really the focus of this chapter, because we covered them rather extensively in Chapter 6 and Chapter 7. However, when Scala developers develop their own classes, they are more likely than not to use their own classes in collections. This example should demonstrate how well Scala collections work for not only the core Scala types, but any other classes you define yourself.
Let’s round up our introduction to classes by working through examples of inheritance and polymorphism. A class can extend up to one other class in Scala with the extends
keyword, and override (i.e., supplant) the behavior of an inherited method with the override
keyword. The fields and methods in a class can be accessed (if strictly necessary) with the this
keyword, while the fields and methods in the parent class(es) can be accessed with the super
keyword. The super
keyword is especially useful when a method needs to still access the similar method in its parent class that it is overriding.
I’ll demonstrate this with a parent class, “A,” and subclass, “C,” and a class situtated between these two, “B”:
scala> class A { | def hi = "Hello from A" | override def toString = getClass.getName | } defined class A scala> class B extends A defined class B scala> class C extends B { override def hi = "hi C -> " + super.hi } defined class C scala> val hiA = new A().hi hiA: String = Hello from A scala> val hiB = new B().hi hiB: String = Hello from A scala> val hiC = new C().hi hiC: String = hi C -> Hello from A
A and B share the same “hi” method, because B inherits its parent’s method. C defines its own version of “hi,” both overriding the version in A and invoking it to include it in the message.
Are the results of these “hi” methods what you expected to see? Seeing an instance of B print out “Hello from A” may be misleading, but this hardcoded message (including the A class name) is what B picked up by extending A. A more informative “hi” method could have included the current class’s name, as our “toString” method did, instead of hardcoding the class name “A.” If we had done that, what do you think the “hi” method would have printed for B and C?
Let’s try out Scala’s polymorphism next, the ability for classes to take on the shape of other, compatible classes. By compatible I mean an instance of a subclass can be used in place of an instance of its parent class, but not the inverse. A subclass extends its parent class, and so supports 100% of the parent’s fields and methods, but the reverse may not be true.
We’ll reuse the A, B, and C classes we defined to test this out:
scala> val a: A = new A a: A = A scala> val a: A = new B a: A = B scala> val b: B = new A <console>:9: error: type mismatch; found : A required: B val b: B = new A ^ scala> val b: B = new B b: B = B
Storing an instance with the same type as its value works every time, as does storing an instance of a subclass into a value with its parent class’s type. However, storing an instance of a parent class into a value of the type of its subclass won’t work. The Scala compiler will correctly point out that an instance of A isn’t compatible with the expected type of B. Another term for this situation is that the instance of A does not conform to the expected type of B. The B class is an extension of A, such that A’s fields and methods are a subset of B’s, not the other way around. The fact that B doesn’t actually add any unique fields or methods doesn’t change this situation.
Let’s put this knowledge to use to create a list of instances of A, B, and C. What should we declare as the type of such a list, A, B, or C?
To ensure that the list can include instances of each of these classes, we should define the list as List[A]
, which is compatible with all of these classes:
scala> val misc = List(new C, new A, new B) misc: List[A] = List(C, A, B) scala> val messages = misc.map(_.hi).distinct.sorted messages: List[String] = List(Hello from A, hi C -> Hello from A)
Whoops! Despite my warning to define the list as List[A]
, I forgot to add an explicit type. Fortunately, the Scala compiler was able to infer the common type of the three instances as being A, the parent class, and set the list’s type parameter correctly. Come to think of it, that’s a great use for the compiler’s type inference feature—finding the lowest (most specific) common denominator of one or more instances.
This wraps up our introduction to classes in Scala. In the rest of the chapter we’ll explore the full syntax for defining classes with fields and methods, the alternate types of classes, and the intricate details of specifying type parameters.
Defining Classes
A class is the definition of a type, containing fields (values and variables) of core types and/or other classes. They also contain methods, functions that may act on the contained fields, and nested class definitions. We’ll start this section with a basic class definition and move on to more parameterized classes.
Syntax: Defining a Simple Class
class <identifier> [extends <identifier>] [{ fields, methods, and classes }]
The classes A, B, and C we defined in the introduction to this chapter demonstrate this class definition (other than the nested classes). The identifier is the class/type name, followed by the class being extended (if any), and then an optional set of curly braces in which are defined the fields and methods for the class. Fields are values or variables, and methods are functions defined as part of the class.
Like expressions and functions, classes can be nested inside each other. A nested class may access the fields and methods of its parent class(es) in addition to its own. In fact, expressions, functions, and classes can be nested inside each other, although it may look odd to have an “if..else” expression block defining and using its own private class.
You can invoke a class’s methods, or access its fields, on an instance of the class, a memory allocation that provides storage for the class’s fields. This action, of reversing memory to allocate a class’s contents, is known as instantiation. Use the new
keyword to instantiate a class by its name, with or without parentheses.
To be more useful, a class should take class parameters, input values used to initialize other fields and methods in the class or even to act as the class’s fields. Class parameters are a comma-delimited list of names and types in the same format as a function’s (and now, also a method’s) input parameters.
Syntax: Defining a Class with Input Parameters
class <identifier> ([val|var] <identifier>: <type>[, ... ]) [extends <identifier>(<input parameters>)] [{ fields and methods }]
A class with input parameters gives a programmer a reason to create multiple instances, because each instance can have its own unique contents. Let’s try creating a class with both value and variable fields as parameters:
scala> class Car(val make: String, var reserved: Boolean) { | def reserve(r: Boolean): Unit = { reserved = r } | } defined class Car scala> val t = new Car("Toyota", false) t: Car = Car@4eb48298 scala> t.reserve(true) scala> println(s"My ${t.make} is now reserved? ${t.reserved}") My Toyota is now reserved? true
The fields and methods of a class can be accessed with standard infix dot notation, where the instance and its field or method are delimited by a period (.
). When invoking an instance’s single-parameter method, infix operator notation may be used as well.
Like functions, class parameters can be invoked with named parameters (see Calling Functions with Named Parameters). Any parameters invoked by position (starting with the first one) must appear before the first named parameter, but following this, named parameters may be used in any order.
As an example, we’ll create a new instance of “Car” using named parameters in the opposite order of their position:
scala> val t2 = new Car(reserved = false, make = "Tesla") t2: Car = Car@2ff4f00f scala> println(t2.make) Tesla
When you have classes that extend classes which take parameters, you’ll need to make sure the parameters are included in the classes’ definition. The class identified following the extends
keyword should have its own set of input parameters as necessary.
In this example we have a new subclass of Car
titled Lotus
that specifies its parent’s input parameters in the definition. I’ll include the Car
class definition for reference:
scala> class Car(val make: String, var reserved: Boolean) { | def reserve(r: Boolean): Unit = { reserved = r } | } defined class Car scala> class Lotus(val color: String, reserved: Boolean) extends Car("Lotus", reserved) defined class Lotus scala> val l = new Lotus("Silver", false) l: Lotus = Lotus@52c46334 scala> println(s"Requested a ${l.color} ${l.make}") Requested a Silver Lotus
Our new subclass, Lotus
, has its own new field, color
, and takes a nonfield input parameter to initialize its parent class, Car
.
In addition to input parameters, another feature that class parameters borrow from functions is the ability to define default values for parameters (see Parameters with Default Values). This allows callers to instantiate the class without specifying all of the class’s parameters.
Syntax: Defining a Class with Input Parameters and Default Values
class <identifier> ([val|var] <identifier>: <type> = <expression>[, ... ]) [extends <identifier>(<input parameters>)] [{ fields and methods }]
Let’s redefine the Car
class to use a default value for the “reserved” field, making it possible to instantiate the class with only the “make” field specified. We’ll add a third field, also with a default value, so we can really experiment with mixing default and required parameters:
scala> class Car(val make: String, var reserved: Boolean = true, | val year: Int = 2015) { | override def toString = s"$year $make, reserved = $reserved" | } defined class Car scala> val a = new Car("Acura") a: Car = 2015 Acura, reserved = true scala> val l = new Car("Lexus", year = 2010) l: Car = 2010 Lexus, reserved = true scala> val p = new Car(reserved = false, make = "Porsche") p: Car = 2015 Porsche, reserved = false
The borrowing of features from functions for class definitions doesn’t end with named parameters and default values, however. Type parameters (see Type Parameters), those nondata specifiers of input or return types in functions, are also available in class definitions.
Come to think of it, you have already used classes that have type parameters, so this shouldn’t be a surprise. The most common one we’ve used is List[A]
, which uses a type parameter to determine the type of its elements and thus operations. For example, a List[String]
may contain String
instances and support operations that take and return a String
.
Let’s revise our ever-growing class definition syntax to include support for one or more type parameters in a class.
Syntax: Defining a Class with Type Parameters
class <identifier> [type-parameters] ([val|var] <identifier>: <type> = <expression>[, ... ]) [extends <identifier>[type-parameters](<input parameters>)] [{ fields and methods }]
You have seen collections as an example of classes using type parameters. A new list of integers will have the Int
type parameter, List[Int](1, 2, 3)
.
Let’s create our own collection and use a type parameter to ensure type safety. The new collection will extend Traversable[A]
, the parent class of Iterable
(see Chapter 6).
Of course, there’s not a lot of traversing going on due to only having one element. However, by extending this base collection we can pick up all of the useful collection operations we have become accustomed to using:
scala> class Singular[A](element: A) extends Traversable[A] { | def foreach[B](f: A => B) = f(element) | } defined class Singular scala> val p = new Singular("Planes") p: Singular[String] = (Planes) scala> p foreach println Planes scala> val name: String = p.head name: String = Planes
A good example of passing a type parameter to the parent class in the class definition.
By defining a
foreach()
operation,Traversable
will ensure our class is a real collection and can use this to enable every other collection operation.Here is a validation of our type-parameterized class, with the REPL printing the class name and the name of the parameterized type used to instantiate it (a
String
).An example usage of the
foreach
method we defined, reduced to its most unadorned invocation.Another example usage of
foreach
, indirectly this time, as we accessTraversable.head
, which invokesforeach
for us. By extendingTraversable
we can accesshead
and a range of other standard collection operations.
At this point we have covered named classes, inheritance, instantiation, input parameters, and type parameters. Believe it or not, there are still many other ways to customize your class definitions in Scala that you’ll need to know. For example, controlling the levels of encapsulation (i.e., privacy) and building layers of abstraction, or defining methods in such a way that they can be accessed without their name! Read on for some of the really interesting features of object-oriented Scala.
More Class Types
Scala offers more than the basic class definitions we have tried out until now. In this section we will look at alternative ways to define and create classes.
Abstract Classes
An abstract class is a class designed to be extended by other classes but not instantiated itself. Abstract classes are designated so by the abstract
keyword, placed before the class
keyword when defining the class.
An abstract class can be used to define the core fields and methods required by its subclasses without providing an actual implementation. Thanks to polymorphism, a value with the type of the abstract class can actually point to an instance of one of its nonabstract subclasses, and invoke methods that actually end up being invoked on the subclass.
Abstract classes provide unimplemented fields and methods by declaring them without defining them. A declared field or method will include the name and parameters but not a starting value or implementation, respectively. A class that extends an abstract class with declared fields and methods, and is not itself marked as abstract, must provide their implementations. An abstract class can also have its own implemented fields and methods, which would not require implementations in subclasses.
Let’s create our own abstract class with a declared value and method, and experiment with implementations:
scala> abstract class Car { | val year: Int | val automatic: Boolean = true | def color: String | } defined class Car scala> new Car() <console>:9: error: class Car is abstract; cannot be instantiated new Car() scala> class RedMini(val year: Int) extends Car { | def color = "Red" | } defined class RedMini scala> val m: Car = new RedMini(2005) m: Car = RedMini@5f5a33ed
An experiment with instantiating our abstract class Car
by itself didn’t work, for the obvious reason that it is abstract and uninstantiatable. Still, it’s nice to see that Scala’s compiler pointed this out with a helpful message.
Creating a subclass that extends Car
but adds a value parameter and a concrete implementation of the color
method solved the problem. The RedMini
class is a successful implementation of its parent abstract class and can be instantiated with only its year as a parameter.
On the other hand, what good is an automobile that only comes in a single color? A better version of a subclass should take the color as an input parameter. Let’s make that change with a new subclass:
scala> class Mini(val year: Int, val color: String) extends Car defined class Mini scala> val redMini: Car = new Mini(2005, "Red") redMini: Car = Mini@1f4dd016 scala> println(s"Got a ${redMini.color} Mini") Got a Red Mini
Our new class, “Mini,” now takes the color as an input parameter.
Wait, Did You Just Implement an Abstract Method with a Value?
Invoking a parentheses- and parameter-free method on an instance has the same appearance as accessing one of its values, so it should be unsurprising that you can implement a required method using a value. The syntax is the same to callers, and because parenthesis-free methods are not expected to have side effects (see Functions with Empty Parentheses), the behavior should be the same.
Abstract classes are a useful tool in object-oriented design, making it possible to create a usable base type while delegating the implementation.
Anonymous Classes
In the previous section we saw a class definition, Mini
, that implemented its parent class’s declared methods. A less formal way to provide the implementation for a parent class’s methods is with an anonymous class, a nonreusable and nameless class definition.
To define a one-time anonymous class, instantiate the parent (and potentially abstract) class and follow the class name and parameters with curly braces containing your implementation. The result is an instance that does extend the given parent class with a one-time implementation, but can be used like an instance from a traditional class definition.
Let’s try it out with a “listener” class, a design pattern for sending notifications that is popularly used in Java applications:
scala> abstract class Listener { def trigger } defined class Listener scala> val myListener = new Listener { | def trigger { println(s"Trigger at ${new java.util.Date}") } | } myListener: Listener = $anon$1@59831016 scala> myListener.trigger Trigger at Fri Jan 24 13:08:51 PDT 2014
The myListener
value is a class instance, but its class definition is part of the same expression that instantiated itself. To create a new myListener
it would be necessary to redefine the anonymous class again.
Here’s a more illustrative example of when you may find it useful to create an anonymous class. We have a class, Listening
, that can register a Listener
and trigger it later as necessary. Instead of instantiating the anonymous class on one line and passing it to the registration function on another, we can combine these into a single step of defining the anonymous class as part of the method invocation. This should look familiar to those with JavaScript experience, especially if you have worked on jQuery-style event handlers:
scala> abstract class Listener { def trigger } defined class Listener scala> class Listening { | var listener: Listener = null | def register(l: Listener) { listener = l } | def sendNotification() { listener.trigger } | } defined class Listening scala> val notification = new Listening() notification: Listening = Listening@66596c4c scala> notification.register(new Listener { | def trigger { println(s"Trigger at ${new java.util.Date}") } | }) scala> notification.sendNotification Trigger at Fri Jan 24 13:15:32 PDT 2014
With anonymous classes, class definitions don’t need to be stable or reusable. When a subclass will only be needed once, the anonymous class syntax can help to simplify your code base.
More Field and Method Types
We just covered alternative class types, but there’s also alternative fields and methods you can use. Let’s have a look at some of the additional choices of fields (values and variables) and methods available in classes.
Overloaded Methods
An overloaded method is a strategy for providing choices to callers. A class may have two or more methods with the same name and return value but with different arrangements of input parameters. By overloading a method name with multiple implementations, multiple choices for invoking a method with a specific name are made available.
Here is an example of overloaded methods, where the methods share the same name but take different parameters. In the example the second overloaded method calls the first after modifying its input parameters appropriately:
scala> class Printer(msg: String) { | def print(s: String): Unit = println(s"$msg: $s") | def print(l: Seq[String]): Unit = print(l.mkString(", ")) | } defined class Printer scala> new Printer("Today's Report").print("Foggy" :: "Rainy" :: "Hot" :: Nil) Today's Report: Foggy, Rainy, Hot
It is not possible to have two methods with the same name and input parameters, but different return values. Doing so will cause a Scala compiler error, because there is no way for only one of the methods to be specifically selected during compilation.
Overloading may be a useful feature, but many Scala developers prefer to use default-value parameters versus overloading. A method that provides default values for its parameters instead of two methods (where one method without a parameter could call the other method, giving the default value) results in less unnecessary code being written.
Apply Methods
Methods named “apply,” sometimes referred to as a default method or an injector method, can be invoked without the method name. The apply method is essentially a shortcut, providing functionality that can be triggered using parentheses but without a method name.
Let’s try it with a class that multiplies numbers by a predefined amount:
scala> class Multiplier(factor: Int) { | def apply(input: Int) = input * factor | } defined class Multiplier scala> val tripleMe = new Multiplier(3) tripleMe: Multiplier = Multiplier@339cde4b scala> val tripled = tripleMe.apply(10) tripled: Int = 30 scala> val tripled2 = tripleMe(10) tripled2: Int = 30
Our “tripleMe” instance can be used with or without the “apply” name to triple a given number. You might remember this syntax from retrieving an element from a list by its index, which happens to use the List.apply
method:
scala> val l = List('a', 'b', 'c') l: List[Char] = List(a, b, c) scala> val character = l(1) character: Char = b
Here, the List.apply(index)
method provides access to an element by index, an operation so common that it makes a good candidate for being the default method of lists.
One potential disadvantage to making a method be the default one is if it makes the code look odd. Accessing the default method should be natural, like the accessor method for lists. Try to only use the apply method where it makes sense, like an accessor method for a list.
Lazy Values
We’ve looked at some really interesting things you can do with methods in Scala, but now let’s see what you can do with fields. The fields (values and variables) we have used so far in classes are all created when the class is first instantiated. Lazy values, however, are only created the first time they are instantiated. You can create a lazy value by adding the keyword lazy
before the val
keyword when defining a value.
In a way, lazy values are a mechanism situated between regular class values and methods. The expression used to initialize a regular class value is only executed once and at instantiation time, whereas the expression that makes up a method is executed every time the method is invoked. However, the expression that initializes a lazy value is executed when the value is invoked, but only the very first time. In this way, a lazy value is a sort of cached function result.
This concept is perhaps better explained with an example. Here’s one that shows when a regular value is calculated versus a lazy value:
scala> class RandomPoint { | val x = { println("creating x"); util.Random.nextInt } | lazy val y = { println("now y"); util.Random.nextInt } | } defined class RandomPoint scala> val p = new RandomPoint() creating x p: RandomPoint = RandomPoint@6c225adb scala> println(s"Location is ${p.x}, ${p.y}") now y Location is 2019268581, -806862774 scala> println(s"Location is ${p.x}, ${p.y}") Location is 2019268581, -806862774
Our class, RandomPoint
, initializes its two fields with expressions that print a message before returning their randomly generated number. The “x” field, a regular value, is initialized when our instance “p” is created. The “y” field, a lazy value, is initialized the first time we access it, but only the first time. In the second printout, both values have been initialized and are stable.
Lazy values are a great way to ensure that time- or performance-sensitive operations can be executed only once in a class’s lifetime. They are popularly used to store information such as file-based properties, open database connections, and other immutable data that should only be initialized if it is really necessary. By initializing this data in a lazy val’s expression, you can ensure that it will only operate if the lazy val is accessed at least once in the class instance’s lifetime.
Packaging
We have covered myriad ways to define classes, methods, and fields. After creating your own classes, at some point you’ll want to start organizing them to prevent namespace cluttering.
Packages are Scala’s (and Java’s) system for code organization. They make it possible to organize Scala code by directory using period-separated paths. Use the package
keyword at the top of a Scala source file to declare all classes in that file to be included in the package.
Syntax: Defining the Package for a Scala File
package <identifier>
Scala follows the Java standard for package naming, where packages start with the reverse domain of your organization or business and then are further classified with additional names on the path. For example, a Scala class that provides utility methods and is developed at Netflix might be packaged in “com.netflix.utilities.”
Scala source files should be stored in directories that match their packages. For example, a “DateUtilities” class in the “com.netflix.utilities” package should be stored under com/netflix/utilities/DateUtilities.scala. The Scala compiler will store the generated .class files (the standard binary format for JVM-executable code) in a directory structure that matches the package.
Let’s try this out by creating a source file with a package and compiling it. We’ll use the scalac
command to compile the source file and generate a class file local to the current directory:
$ mkdir -p src/com/oreilly $ cat > src/com/oreilly/Config.scala package com.oreilly class Config(val baseUrl: String = "http://localhost") $ scalac src/com/oreilly/Config.scala $ ls com/oreilly/Config.class com/oreilly/Config.class
The src directory is a nice way to separate the source code from whatever else is in the current directory, but it wasn’t actually used by the compiler. It took the relative path to the source file, compiled it, and generated a class file relative to the directory you launched the compiler from.
Accessing Packaged Classes
A packaged class can be accessed by its full period-delimited package path and class name. In the preceding “Config” example, the class named “Config” can be accessed as “com.oreilly.Config.”
Let’s try this out by accessing the JDK’s Date
class, located in the java.util
package:
scala> val d = new java.util.Date d: java.util.Date = Wed Jan 22 16:42:04 PDT 2014
A more convenient way to access classes in other packages is to import them into the current namespace. That way, the class can be accessed without its package prefix. To import a class, use the import
keyword followed by the full package and name of the class.
Syntax: Importing a Packaged Class
import <package>.<class>
Let’s create a new Date
, but only after importing the class into the namespace so we can refer to it by name:
scala> import java.util.Date import java.util.Date scala> val d = new Date d: java.util.Date = Wed Jan 22 16:49:17 PDT 2014
The Date
class we are instantiating still lives in its java.util
package, but is now also part of the current namespace.
The import
command is a statement, because it doesn’t return a value. Unlike in Java (which has a similar import
keyword), an import
can be placed anywhere in your code where you might use a statement.
Let’s exercise the ability to place imports wherever we might use any other statement. In this example I’ll add an import for Java’s UUID class in the middle of a println
call:
scala> println("Your new UUID is " + {import java.util.UUID; UUID.randomUUID}) Your new UUID is 47ba6844-3df5-403e-92cc-e429e614c9e5
You may not always want to add imports in the restricted scope of a function call. However, adding your imports near the code where you are using the imported classes helps to make the intent of the import more clear. It may also prevent name conflicts caused by importing multiple classes of the same name from different packages. By adding the conflicting imports in separate scopes, as opposed to adding them at the top of the file, the classes can be used without conflict.
An alternative to importing the full package and class is to import part of the package, reducing the need to refer to the full package but not quite importing a class. Scala’s imports are accumulative, so importing a package allows us to remove that package from the full path of a class in the package.
Here’s the Date
class again, accessed by its partial package path:
This Is a Good Time to Reset Your REPL Session and Namespace
If you’re trying out these examples in the Scala REPL (and if you’re not, why not?), you’ll need to reset your session to verify that imports are working properly. This will clear out all previous imports and allow you to test only the new imports you have defined. To do so, type :reset
to reset the session before reimporting a class into the REPL’s namespace.
scala> import java.util import java.util scala> val d = new util.Date d: java.util.Date = Wed Jan 2229 06:18:52 PDT 2014
Our accumulative import worked; we can now access classes in the java.util
package using the util
package alone.
Scala also supports importing the entire contents of a package at once with the underscore (_
) operator. After doing so, every class in that package will be added to the namespace. You might remember when we used this to import multiple Future
helper classes (see Handling futures synchronously) without importing them one at a time.
Let’s use the import-all feature to import all of the mutable collections into our current namespace, and then experiment with the ArrayBuffer
and Queue
collections in that package:
scala> import collection.mutable._ import collection.mutable._ scala> val b = new ArrayBuffer[String] b: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer() scala> b += "Hello" res0: b.type = ArrayBuffer(Hello) scala> val q = new Queue[Int] q: scala.collection.mutable.Queue[Int] = Queue() scala> q.enqueue(3, 4, 5) scala> val pop = q.dequeue pop: Int = 3 scala> println(q) Queue(4, 5)
The ArrayBuffer
and Queue
collections, fully packaged classes, are now accessible by name without explicitly importing both classes from their package. Of course, they aren’t the only ones we could have used. Because we imported the entire contents of the collection.mutable
package, the full range of mutable collections became available in our namespace.
Speaking of ArrayBuffer
, did you notice that we imported everything in the collection.
mutable
package but the REPL printed its full class name as scala.
collection.
mutable.ArrayBuffer
? Scala does its own automatic imports in every Scala class, importing the entire scala._
and java.lang._
packages. This makes it possible to access the classes and packages in scala
and java.lang
directly without using the full path. Thus, Scala’s class for random-based utilities is at scala.util.
Random
but can be accessed as util.Random
. Likewise, the class we use for sleeping the current thread is officially defined as java.lang.Thread
but we can access it directly by its class name.
There is a potential downside to importing every class and subpackage from a package. If the package you’re importing has a class name that duplicates one already in your namespace, the class that was already in your namespace will no longer be accessible. As an example, the collection.mutable
package has a mutable version of Map
with the same name, Map
. After importing the entire mutable package, any Map
I create would then be mutable. This may be the desired behavior, but in case it isn’t, make sure to check the contents of packages that you mass-import.
An alternative to importing a full package is to use an import group. With this feature, you can list a group of class names to import intead of a complete package.
Syntax: Using an Import Group
import <package>.{<class 1>[, <class 2>...]}
With an import group I could have imported the Queue
and ArrayBuffer
collections directly without importing the mutable Map
:
scala> import collection.mutable.{Queue,ArrayBuffer} import collection.mutable.{Queue, ArrayBuffer} scala> val q = new Queue[Int] q: scala.collection.mutable.Queue[Int] = Queue() scala> val b = new ArrayBuffer[String] b: scala.collection.mutable.ArrayBuffer[String] = ArrayBuffer() scala> val m = Map(1 -> 2) m: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2)
After importing only the mutable collections that we wanted, we can use the Queue
and ArrayBuffer
mutable collections while still accessing the Map
immutable collection. In this example the import group is just a shortcut that saves us one line of code, but with several classes from the same package they can visibly reduce the size of an “import” section.
There’s actually a way to add both the immutable and the mutable Map
collections to the current namespace without having a conflict. To do this, use an import alias that renames one of the types inside the local namespace. What’s renamed is the local namespace reference to the class, not the class itself, so there is no actual change to classes outside your namespace (typically the file you are editing).
Syntax: Using an Import Alias
import <package>.{<original name>=><alias>}
Let’s use an import alias to bring the collection.mutable.Map
collection into our namespace, but in a way that won’t conflict with our standard immutable Map
:
scala> import collection.mutable.{Map=>MutMap} import collection.mutable.{Map=>MutMap} scala> val m1 = Map(1 -> 2) m1: scala.collection.immutable.Map[Int,Int] = Map(1 -> 2) scala> val m2 = MutMap(2 -> 3) m2: scala.collection.mutable.Map[Int,Int] = Map(2 -> 3) scala> m2.remove(2); println(m2) Map()
With our aliased collection “MutMap” (short for “mutable,” not “mutt”!) we can create both mutable and immutable maps by name, without specifying their packages.
Knowing how to access classes in other packages (whether your own or a library’s) is a required skill for Scala developers. Organizing your own classes in packages is more of an acquired art, because there is no real guide on how to best do this. I can only offer a recommendation that your code should be organized for findability and abstraction, so developers can find your code and will know which code they should be finding.
Packaging Syntax
We have covered the most popular form of stating the package for one or more classes, the package <identifier>
command at the top of the file. Everything that follows in the file will then be considered as a member of that identified package.
A less common form of specifying a package is with the packaging syntax, where the package is a block that wraps its classes with curly braces. In this format, only the classes within the package block are designated to be members of that package. This makes it possible for the same file to contain classes that are members of different packages. It also makes it possible to clearly demarcate packages within a nonfile environment such as a REPL.
Syntax: Packaging Classes
package <identifier> { <class definitions> }
Let’s rewrite our “Config” example (from Packaging) using packaging syntax. Because we aren’t relying on a file to delimit the end of the package, we can write the entire package in the REPL:
The Scala REPL Requires “Raw” Paste Mode for Packages
Packages are traditionally used to mark files, and thus are unsupported in the standard editing mode in the REPL. The workaround is to enter the “raw” paste mode with :paste -raw
and then paste the contents of a Scala file, which will be fully compiled but available from the REPL.
scala> :paste -raw // Entering paste mode (ctrl-D to finish) package com { package oreilly { class Config(val baseUrl: String = "http://localhost") } } // Exiting paste mode, now interpreting. scala> val url = new com.oreilly.Config().baseUrl url: String = http://localhost
Our new class is now available at com.oreilly.Config
and clearly packaged.
Would you expect that packaging syntax can be nested? Given that expressions, functions, and class definitions can be nested this shouldn’t come as a surprise, but you can indeed nest your packages. A benefit to nesting packages is that the package path is derived from the sum of the nested package names. Thus a multipart package like “com.oreilly” can be built from an outer package of “com” and inner package of “oreilly.”
Okay, let’s try that out. We’ll enter the “raw” paste mode again to enable package support in the REPL:
scala> :paste -raw // Entering paste mode (ctrl-D to finish) package com { package oreilly { class Config(val baseUrl: String = "http://localhost") } } // Exiting paste mode, now interpreting. scala> val url = new com.oreilly.Config().baseUrl url: String = http://localhost
We now have two ways to define packages for our classes: by file and by packaging syntax. Although the former tends to be the most popular among Scala developers (in my opinion), both versions are acceptable and end up with the exact same result after compilation.
Privacy Controls
A corollary to packaging code is using privacy controls to manage its access. While you are organizing your code into separate packages (or subpackages), you’ll probably find certain functionality in one package that ought to be hidden from other packages. For example, low-level persistence code could be hidden from your user interface-level code to force your layers to use a middle layer for communication. Or you may want to limit who can extend a subclass so that your parent class can keep track of its implementers.
By default, Scala does not add privacy controls. Any class you write will be instantiable and its fields and methods accessible by any other code. If you have a class with stateless methods, such as utility functions, this may be perfectly acceptable to you.
If you do have some reason to add privacy controls, such as mutable state that should only be handled inside the class, you can add them on a field and method basis in your class.
One privacy control is marking fields and methods as protected, which will limit their access to code from the same class or its subclasses. No other code except that class or subclasses will have access. Use the protected
keyword before a val
, var
, or def
keyword to mark that entity as being protected.
Here’s an example of protecting a field from access by outside classes. The field is still accessible by a subclass, however:
scala> class User { protected val passwd = util.Random.nextString(10) } defined class User scala> class ValidUser extends User { def isValid = ! passwd.isEmpty } defined class ValidUser scala> val isValid = new ValidUser().isValid isValid: Boolean = true
To verify that the “passwd” field is only accessible to “User” and its subclasses, try creating a new instance of “User” and access its protected field directly. You should see an error from the compiler alerting you that the “passwd” field is not accessible from outside the class (or subclasses).
When you need a more stringent level of protection, mark fields and methods as private to limit their access to only the class in which they are defined. No other code outside the class, and not even subclasses, will have access to that field.
Let’s take another stab at this “User” class. If we’re really storing a password in plain text in a class, making it accessible to any subclass means that any code anywhere can subclass our class and get access to it. Of course you would need an instance of that subclass to access the field, but this could still be a problem for some applications. In our new version, we’ll fix this by making the password private so only our “User” class can get to it. We’ll also make it mutable, adding a public setter method with an alerting system so we’ll be able to check the logs for password changes. Finally, we’ll add a validation system, again without exposing our private (literally!) password to external reads or writes:
scala> class User(private var password: String) { | def update(p: String) { | println("Modifying the password!") | password = p | } | def validate(p: String) = p == password | } defined class User scala> val u = new User("1234") u: User = User@94f6bfb scala> val isValid = u.validate("4567") isValid: Boolean = false scala> u.update("4567") Modifying the password! scala> val isValid = u.validate("4567") isValid: Boolean = true
By removing access to the password field, the User
class is made (slightly) more secure and more flexible. With the backing data decoupled from the methods, you have the ability to change where you’re storing the data, such as a secure identification system, without changing how callers access the class. Protecting mutable state from unplanned changes and providing the ability to decouple it from its current usage are only some of the benefits of encapsulation and privacy control.
Privacy Access Modifiers
The private
and protected
keywords provide class-hierarchy restrictions, but there are times when you want more fine-grained access control for your class’s members. For example, a class in a persistence package may only want to reveal some of its database-level methods to other classes in the same package to reduce bugs and ensure a single point of access.
You can add this level of control by specifying access modifiers in addition to your private
or protected
designation. An access modifier specifies that such a designation is only active up to a given point, such as a package, class, or instance, and then is inactive within that point. For example, a method may be private but only so for callers outside its package (i.e., “up to” its package), and then freely accessible within the package. A field may be marked as private not just within the package but from other instances of the same class, and thus can only be accessed from code within the same instance.
An additional benefit of access modifiers is that they enable access controls for classes. There’s not much benefit to marking a class as private for everyone (how would you go about instantiating it?), but a class marked as private for everything outside its package could be very useful.
To specify an access modifier, write the name of the package or class, or else use this
inside brackets after the private
or protected
keyword. The package or class name will specify that the keyword is only active up to that package or class (and freely available within), but this
limits access to only the same instance.
Let’s try this out with an example of specifying both package-level and instance-level protections. We’ll use the packaging syntax to denote the class’s package, and thus the “raw” paste mode in the REPL to support the packaging:
scala> :paste -raw // Entering paste mode (ctrl-D to finish) package com.oreilly { private[oreilly] class Config { val url = "http://localhost" } class Authentication { private[this] val password = "jason" // TODO change me def validate = password.size > 0 } class Test { println(s"url = ${new Config().url}") } } // Exiting paste mode, now interpreting. scala> val valid = new com.oreilly.Authentication().validate valid: Boolean = true scala> new com.oreilly.Test url = http://localhost res0: com.oreilly.Test = com.oreilly.Test@4c309d4d scala> new com.oreilly.Config <console>:8: error: class Config in package oreilly cannot be accessed in package com.oreilly new com.oreilly.Config ^
Access to the “Config” class is now restricted to the “com.oreilly” package. Only the last part of the package path is required here.
Our secretive “password” field is now off-limits to everyone except code within the same instance of the same class.
Here we are verifying “password” access from the same instance.
The “Test” class was able to successfully instantiate a “Config” class…
… but we were not able to do the same from outside the package.
Scala’s access modifiers provide a useful complement to the notion of the strict access policy for private
members and inheritance access policy for protected
members. In the case of package-level protection, these policies can be overridden based on the proximity of another class. Instance-level protection, on the other hand, adds an additional restriction to these policies based on the actual instances of the classes. Using access modifiers to either loosen or restrict access can be helpful, if used correctly, in improving the encapsulation and security of your applications.
Final and Sealed Classes
The protected
and private
access controls and their modifiers can limit access to a class or its members overall or based on location. However, they lack the abililty to restrict creating subclasses. Well, unless you mark a class as being private outside of its package, but then it can neither be subclassed nor used in that circumstance.
Final class members can never be overridden in subclasses. Marking a value, variable, or method with the final
keyword ensures that the implementation is the one that all subclasses will use. Entire classes can be marked as final as well, preventing any possible subclasses of that class.
If final classes are too restrictive for your needs, consider sealed classes instead. Sealed classes restrict the subclasses of a class to being located in the same file as the parent class. By sealing a class, you can write code that makes safe assumptions about its hierarchy. Classes are sealed by prefixing the class definition and class
keyword with the sealed
keyword.
One popular sealed class is Option
, one of the monadic collections we covered in Monadic Collections. The Option
class is both abstract and sealed, and implemented with the (proper!) assumption that it will only ever have two subclasses, Some
and None
. By ensuring that no other subclasses will ever exist, Option
can refer to these implementations explicitly in its code. An unsealed version of this collection would be more difficult to implement, because anyone could add an extra subclass that may not follow the assumed behavior of Some
and None
.
As with the Option
implementation, sealed classes are a useful way to implement an abstract parent class that “knows” and refers to specific subclasses. By restricting subclasses outside the same file, assumptions can be made about a class hierarchy that would otherwise have severe repercussions (read: bugs).
Summary
Classes are often a starting point for learning a programming language. Because they are built on a foundation of values and functions, however, it seemed more appropriate to cover those features first. Now that you have a solid understanding of classes, it is safe to point out that values and functions (now “methods”) don’t really exist outside of classes. Classes are the core building blocks of Scala applications, and values and methods make up their bodies.
They are not, however, the exclusive containers of values and methods. In the next chapter we’ll explore how objects, the singletons of the Scala world, can be used alone or alongside classes. We’ll also see how traits can contain their own values and functions before being combined and mixed into classes.
Exercises
We’re working on a gaming site, and need to track popular consoles like the Xbox Two and Playstation 5 (I’m planning for the future here).
Create a console class that can track the make, model, debut date, WiFi type, physical media formats supported, and maximum video resolution. Override the default
toString
method to print a reasonably sized description of the instance (< 120 chars).-
The debut date (or launch date) should be an instance of
java.util.Date
. - Keep the WiFi type (b/g, b/g/n, etc.) field optional, in case some consoles don’t have WiFi.
-
The physical media formats should be a list. Is a
String
the best bet here, or anInt
that matches a constant value? - The maximum video resolution should be in a format that would make it possible to sort consoles in order of greatest number of pixels.
-
The debut date (or launch date) should be an instance of
- Test your new console class by writing a new class that creates four instances of this console class. All of the instances should have reasonably accurate values.
- Now it’s time for games. Create a game class that includes the name, maker, and a list of consoles it supports, plus an “isSupported” method that returns true if a given console is supported.
- Test out this game class by generating a list of games, each containing one or more instances of consoles. Can you convert this list to a lookup table for consoles with a list of supported games? How about a function that prints a list of the games, sorted first by maker and then by game name?
Create a linked list, object-oriented-style.
Create a container class that has an instance of itself plus an instance of a parameterized type. The constructor should take a variable number of the instances (e.g., strings or ints or any other parameterized type), which can be implemented with vararg parameters (see Vararg Parameters). Implement a “foreach” method that users can call to iterate over the list, invoking their function for every element.
- How will you determine the end of the list?
-
C-style lists often use a
null
value to denote the end of the list. Is that the best approach here? -
Do you have a good use for the
apply()
method here?
I’m sure your linked list works great, but let’s try refactoring it with a more interesting approach. Make your container class abstract with two subclasses: one representing a node with a valid item and one representing a node without a valid item, signifying the last item in the list.
- Will you ever need more than one instance of the second subclass?
- Are there any helper methods that should be private?
- How about abstract methods that the subclasses will need to implement?
-
If you implemented the
apply()
method, should each subclass have its own implementation?
-
Add the standard
head
,tail
,filter
,size
, andmap
collection methods for your linked list. Can you implement any of these using lazy values? Which of these should be implemented in the parent class versus being implemented in its subclasses? -
Implement the
head
,tail
,filter
,size
, andmap
collection methods using recursion instead of iteration. Can you ensure these all use tail recursion (see Recursive Functions) to prevent stack overflow errors for massive collections?
For a change of pace, let’s create a directory listing class. The constructor fields should be the full path to the directory and a predicate function that takes a
String
(the filename) and returns true if the file should be included. The method “list” should then list the files in the directory.To implement this, create an instance of
java.io.File
and use itslistFiles(filter: FilenameFilter)
to list files that match the given filter. You’ll find Javadocs for this method and for thejava.io.FilenameFilter
class, but you will need to figure out how this would be called from Scala. You should pass in theFilenameFilter
argument as an anonymous class.- Is there any part of this class that would work well as a lazy value?
-
Would it make sense to store the anonymous subclass of
java.io.FilenameFilter
as a lazy val? - How about the filtered directory listing?
The JVM library includes a working MIDI sound synthesizer. Here’s an example of playing a short set of notes:
scala> val synth = javax.sound.midi.MidiSystem.getSynthesizer synth: javax.sound.midi.Synthesizer = com.sun.media.sound .SoftSynthesizer@283a8ad6 scala> synth.open() scala> val channel = synth.getChannels.head channel: javax.sound.midi.MidiChannel = com.sun.media.sound .SoftChannelProxy@606d6d2c scala> channel.noteOn(50, 80); Thread.sleep(250); channel.noteOff(30) scala> synth.close()
Create a simpler interface to this by writing a class that plays a series of notes. The class’s constructor should take the volume (set to 80 in the example) but always use the same duration (250 milliseconds in the example). Its “play” method should take a list of the notes, for example
Seq(30, 35, 40, 45, 50, 55, 60, 65, 70)
, and play them in the synthesizer.-
Assume the
getSynthesizer
method call is expensive. How can you prevent unnecessarily calling it in case the “play” method is never called? - Make sure to hide fields that callers don’t need to know about.
-
Can you support a
Range
as input, e.g.,play(30 to 70 by 5)
? - Can you support multiple ranges, for example a series of notes that rise, fall, and then rise again?
- Assume we only ever need one instance, ever, with the volume set to 95. Can you use access controls to ensure that there will never be more than one instance of this class?
-
Assume the
Get Learning Scala now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.