Scala is an object-oriented language like Java, Python, Ruby, Smalltalk, and others. If you’re coming from the Java world, you’ll notice some notable improvements over the limitations of Java’s object model.
We assume you have some prior experience with object-oriented programming (OOP), so we will not discuss the basic principles here, although some common terms and concepts are discussed in the Glossary. See [Meyer1997] for a detailed introduction to OOP, see [Martin2003] for a recent treatment of OOP principles in the context of “agile software development”, see [GOF1995] to learn about design patterns, and see [WirfsBrock2003] for a discussion of object-oriented design concepts.
Let’s review the terminology of OOP in Scala.
We saw previously that Scala has the concept of a declared object
, which we’ll dig into in the section called “Classes and Objects: Where Are the Statics?”. We’ll use the term instance to refer to a class instance generically, meaning either an object
or an instance of a class
, to avoid the potential for confusion between these two concepts.
Classes are declared with the keyword class
. We will see later that additional keywords can also be used, like final
to prevent creation of derived classes and abstract
to indicate that the class can’t be instantiated, usually because it contains or inherits member declarations without providing concrete definitions for them.
An instance can refer to itself using the this
keyword, just as in Java and similar languages.
Following Scala’s convention, we use the term method for a function that is tied to an instance. Some other object-oriented languages use the term “member function”. Method definitions start with the def
keyword.
Like Java, but unlike Ruby and Python, Scala allows overloaded methods. Two or more methods can have the same name as long as their full signatures are unique. The signature includes the type name, the list of parameters with types, and the method’s return value.
Not true, due to erasure, you cannot have two identically named methods with a parameter of type List[Foo] in one, and List[Bar] in the other.
There are a few more corner cases, I explore some here: http://github.com/retronym/scala-sandbox/blob/dee7f514c9321648d041fbd027909949cf85509d/lessons/src/main/scala/retronym/lessons/overload/Overload.scala
In particular, I would mention that vals, vars, and methods share a single namespace, perhaps with a pointer to a section describing the Uniform Access Principle. I would mention at this stage that
Henrik, very true. Will add text for it. Thanks. Jason, very helpful example. Thanks. We discuss the namespace issue later. I'll include links to those areas.
However, there is an exception to this rule due to type erasure, which is a feature of the JVM only, but is used by Scala on both the JVM and .NET platforms, to minimize incompatibilities. Suppose two methods are identical except that one takes a parameter of type List[String]
while the other takes a parameter of type List[Int]
, as in the the following example.
// code-examples/BasicOOP/type-erasure-wont-compile.scala // WON'T COMPILE object Foo { def bar(list: List[String]) = list.toString def bar(list: List[Int]) = list.size.toString }
You’ll get a compilation error on the second method because the two methods will have an identical signature after type erasure.
The scala
interpreter will let you type in both methods. It simply drops the first version. However, if you try to load the previous example using the :load file
command, you’ll get the same error scalac
raises.
We’ll discuss type erasure in more detail in Chapter 12, The Scala Type System.
Also by convention, we use the term field for a variable that is tied to an instance. The term attribute is often used in other languages (like Ruby). Note that the state of an instance is the union of all the values currently represented by the instance’s fields.
As we discussed in the section called “Variable Declarations” in Chapter 2, Type Less, Do More, read-only (“value”) fields are declared using the val
keyword and read/write fields are declared using the var
keyword.
Scala also allows types to be declared in classes, as we saw in the section called “Abstract Types And Parameterized Types” in Chapter 2, Type Less, Do More.
We use the term member to refer to a field, method, or type in a generic way. Note that field and method members (but not type members) share the same namespace, unlike Java. We’ll discuss this more in the section called “When Accessor Methods and Fields Are Indistinguishable: The Uniform Access Principle” in Chapter 6, Advanced Object-Oriented Programming In Scala.
Finally, new instances of reference types are created from a class using the new
keyword, as in languages like Java and C#. Note that you can drop the parentheses when using a default constructor (i.e., one that takes no arguments). In some cases, literal values can be used instead, e.g., val name = "Programming Scala"
is equivalent to val name = new String("Programming Scala")
.
Instances of value types (e.g., Int
, Double
, etc.), which correspond to the primitives in languages like Java, are always created using literal values, e.g., 1
, 3.14
, etc.. In fact, there are no public constructors for these types, so an expression like val i = new Int(1)
won’t compile.
We’ll discuss the difference between reference and value types in the section called “The Scala Type Hierarchy”.
Scala supports single inheritance, not multiple inheritance. A child (or derived) class can have one and only one parent (or base) class. The sole exception is the root of the Scala class hierarchy, Any
, which has no parent.
Instead of "A child (or derived) class can have one and only one parent (or base) class.", "All classes have exactly one parent class." would be a more concise 'lead in' to the next sentence: "The sole exception is the root..."
Thanks. will consider this rewording.
I was just wondering why the 'Any' class was allowed to have more than one parent class, 'til I read these comments.
We’ve seen several examples of parent and child classes already. Here are snippets of one of the first examples we saw, in the section called “Abstract Types And Parameterized Types” from Chapter 2, Type Less, Do More.
// code-examples/TypeLessDoMore/abstract-types-script.scala import java.io._ abstract class BulkReader { // ... } class StringBulkReader(val source: String) extends BulkReader { // ... } class FileBulkReader(val source: File) extends BulkReader { // ... }
As in Java, the keyword extends
indicates the parent class, in this case BulkReader
. In Scala, extends
is also used when a class inherits a trait as its parent (even when it mixes in other traits using the with
keyword). Also, extends
is used when one trait is the child of another trait or class. Yes, traits can inherit classes.
Keyword extends does not explicitly / always indicate the parent class. It is also used to mix in a trait (in case a class implicitly / only inherits from Any)
Thanks. will clarify.
If you don’t extend
a parent class, the default parent is AnyRef
, a direct child class of Any
. (We discuss the difference between Any
and AnyRef
when we discuss the Scala type hierarchy in the section called “The Scala Type Hierarchy”.)
Scala distinguishes between a primary constructor and zero or more auxiliary constructors. In Scala, the primary constructor is the entire body of the class. Any parameters that the constructor requires are listed after the class name. We’ve seen many examples of this already, as in the ButtonWithCallbacks
example we used in Chapter 4, Traits.
// code-examples/Traits/ui/button-callbacks.scala package ui class ButtonWithCallbacks(val label: String, val clickedCallbacks: List[() => Unit]) extends Widget { require(clickedCallbacks != null, "Callback list can't be null!") def this(label: String, clickedCallback: () => Unit) = this(label, List(clickedCallback)) def this(label: String) = { this(label, Nil) println("Warning: button has no click callbacks!") } def click() = { // ... logic to give the appearance of clicking a physical button ... clickedCallbacks.foreach(f => f()) } }
You can also see, that you always firstly have to call the primary or another auxiliary constructor when defining an auxiliary constructor.
We'll describe this later.
If a parameter has the var
keyword, a public writer method is also generated with the parameter’s name as a prefix, followed by _=
. For example, if label
were declared as a var
, the writer method would be named label_=
and it would take a single argument of type String
.
It should be specified what happens when you don't use neither val nor val.
True. Will clarify.
There are times when you don’t want the accessor methods to be generated automatically. In other words, you want the field to be private. Add the private
keyword before the val
or var
keyword and the accessor methods won’t be generated. (See the section called “Visibility Rules” below for more details.)
For you Java programmers, Scala doesn’t follow the s [JavaBeansSpec] convention that field reader and writer methods begin with get
and set
respectively, followed by the field name with the first character capitalized. We’ll see why when we discuss the Uniform Access Principle in the section called “When Accessor Methods and Fields Are Indistinguishable: The Uniform Access Principle” below. However, you can get JavaBeans-style getters and setters when you need them using the scala.reflect.BeanProperty
annotation, as we’ll discuss in the section called “JavaBean Properties” in Chapter 14, Scala Tools, Libraries and IDE Support.
When an instance of the class is created, each field corresponding to a parameter in the parameter list will be initialized with the parameter automatically. No constructor logic is required to initialize these fields, in contrast to most other object-oriented languages.
The first statement in the ButtonWithCallbacks
class (i.e., the constructor) body is a test to ensure that a non-null
list has been passed to the constructor. (It does allow an empty Nil
list, however.) It uses the convenient require
function that is imported automatically into the current scope (as we’ll discuss in the section called “The Predef Object” in Chapter 7, The Scala Object System). If the list is null, require
will throw an exception. The require
function and its companion assume
are very useful for design by contract programming, as discussed in the section called “Better Design with Design By Contract” in Chapter 13, Application Design.
Here is part of a full specification for ButtonWithCallbacks
that demonstrates the require
statement in use.
// code-examples/Traits/ui/button-callbacks-spec.scala package ui import org.specs._ object ButtonWithCallbacksSpec extends Specification { "A ButtonWithCallbacks" should { // ... "not be constructable with a null callback list" in { val nullList:List[() => Unit] = null val errorMessage = "requirement failed: Callback list can't be null!" (new ButtonWithCallbacks("button1", nullList)) must throwA( new IllegalArgumentException(errorMessage)) } } }
Scala even makes it difficult to pass null
as the second parameter to the constructor; it won’t type check when you compile it. However, you can assign null
to a value, as shown. If we didn’t have the must throwA(…)
clause, we would see the following exception thrown.
java.lang.IllegalArgumentException: requirement failed: Callback list can't be null! at scala.Predef$.require(Predef.scala:112) at ui.ButtonWithCallbacks.<init>(button-callbacks.scala:7) ....
ButtonWithCallbacks
defines two auxiliary constructors for the user’s convenience. The first auxiliary constructor accepts a label and a single callback. It calls the primary constructor, passing the label and a new List
to wrap the single callback.
The second auxiliary constructor accepts just a label. It calls the primary constructor with Nil
(which represents an empty List
object). The constructor then prints a warning message that there are no callbacks, since lists are immutable and there is no way to replace the callback list val
with a new one.
In order to avoid infinite recursion, Scala requires each auxiliary constructor to invoke another constructor defined before it [ScalaSpec2009]. The constructor invoked may be either another auxiliary constructor or the primary constructor, and it must be the first statement in the auxiliary constructor’s body. Additional processing can occur after this call, such as the warning message printed in our example.
another constructors <-
Because all auxiliary constructors eventually invoke the primary constructor, logic checks and other initializations done in the body will be performed consistently for all instances created.
There are a few advantages of Scala’s constraints on constructors.
val
or a var
, Scala automatically generates a field, the appropriate accessor methods (unless they are declared private
), and the initialization logic for when instances are created.
There is also at least one disadvantage of Scala’s constraints on constructors.
"However, the authors find these circumstances to be rare." sounds a lot like the: "In practice type errors don't happen much." Mantra found in books about 'dynamic languages' vs. the "Static type checking is really useful." found in books about 'static languages'. The readers should know for themselves how often they found a particular language feature useful or not. You shouldn't anchor them.
Simply mentioning that Scala doesn't have this feature, hinting that use might be an indicator for violating the 'single responsibility principle' should be enough.
The primary constructor in a derived class must invoke one of the parent class constructors, either the primary constructor or an auxiliary constructor. In the following example, a class derived from ButtonWithCallbacks
, called RadioButtonWithCallbacks
, invokes the primary ButtonWithCallbacks
constructor. “Radio” buttons can be either on or off.
Creating a dedicated example with shorter names to avoid the line breaks would be nice. It is really hard to find the call to the superclasses primary constructor after this bunch of this relatively complicated type declarations - which are not helpful at all for understanding the currently described concept. Especially if you don't show the definition of the ButtonWithCallbacks class (to allow the reader to see for him/herself which constructor of the parent class is called)
Limited by the print format.
// code-examples/BasicOOP/ui/radio-button-callbacks.scala package ui /** * Button with two states, on or off, like an old-style, * channel-selection button on a radio. */ class RadioButtonWithCallbacks( var on: Boolean, label: String, clickedCallbacks: List[() => Unit]) extends ButtonWithCallbacks(label, clickedCallbacks) { def this(on: Boolean, label: String, clickedCallback: () => Unit) = this(on, label, List(clickedCallback)) def this(on: Boolean, label: String) = this(on, label, Nil) }
The primary constructor for RadioButtonWithCallbacks
takes three parameters: an on
state (true
or false
), a label, and a list of callbacks. It passes the label and list of callbacks to its parent class, ButtonWithCallbacks
. The on
parameter is declared as a var
, so it is mutable. on
is also the one constructor parameter unique to a radio button, so it is kept as an attribute of RadioButtonWithCallbacks
.
class RadioButtonWithCallbacks( var on: Boolean, label: String, clickedCallbacks: List[() => Unit]) extends ButtonWithCallbacks(label, clickedCallbacks)What happened the the val keywords before label and clickedCallbacks. I figured out why they are not used, however they reason deserves a mention
For consistency with its parent class, RadioButtonWithCallbacks
also declares two auxiliary constructors. Note that they must invoke a preceding constructor in RadioButtonWithCallbacks
, as before. They can’t invoke a ButtonWithCallbacks
constructor directly. Declaring all these constructors in each class could get tedious after a while, but we explored techniques in Chapter 4, Traits that can eliminate repetition.
You should drive the point, that only the primary constructor is able (and has to) call the superclasses constructor, more clearly. Maybe even directly at the beginning of the "Calling Parent Class Constructors" section.
This presumably also means that a given subclass may only invoke one of the parent class's constructors. Compare that to, for instance, Java exception hierarchies, where there tends to be a parallel hierarchy of constructors.
While super
is used to invoke overridden methods, as in Java, it cannot be used to invoke a super class constructor.
Scala lets you nest class declarations, like many object-oriented languages. Suppose we want all Widgets
to have a map of properties. These properties could be size, color, whether or not the widget is visible, etc. We might use a simple map
to hold the properties, but let’s assume that we also want to control access to the properties, and to perform other operations when they change.
Here is one way we might expand our original Widget
example from the section called “Traits as Mixins” in Chapter 4, Traits to add this feature.
// code-examples/BasicOOP/ui/widget.scala package ui abstract class Widget { class Properties { import scala.collection.immutable.HashMap private var values: Map[String, Any] = new HashMap def size = values.size def get(key: String) = values.get(key) def update(key: String, value: Any) = { // Do some preprocessing, e.g., filtering. values = values.update(key, value) // Do some postprocessing. } } val properties = new Properties }
We added a Properties
class that has a private, mutable reference to an immutable HashMap
. We also added three public methods that retrieve the size (i.e., the number of properties defined), retrieve a single element in the map, and update the map with a new element, respectively. We might need to do additional work in the update
method, and we’ve indicated as much with comments.
You can see from the above example that Scala allows classes to be declared inside one another, or “nested”. A nested class make sense when you have enough related functionality to lump together in a class, but the functionality is only ever going to be used by its “outer” class.
So far, we’ve covered how to declare classes, how to instantiate them, and some of the basics of inheritance. In the next section, we’ll discuss visibility rules within classes and objects.
For convenience, we’ll use the word “type” in this section to refer to classes and traits generically, as opposed to referring to member type
declarations. We’ll include those when we use the term “member” generically, unless otherwise indicated.
Most object-oriented languages have constructs to constrain the visibility (or scope) of type and type-member declarations. These constructs support the object-oriented form of encapsulation, where only the essential public abstraction of a class or trait is exposed and implementation information is hidden from view.
You’ll want to use public visibility for anything that users of your classes and objects should see and use. Keep in mind that the set of publicly visible members form the abstraction exposed by the type, along with the type’s name itself.
The conventional wisdom in object-oriented design is that fields should be private or protected. If access is required, it should happen through methods, but not everything should be accessible by default. The virtue of the Uniform Access Principle (see the section called “When Accessor Methods and Fields Are Indistinguishable: The Uniform Access Principle”) is that we can give the user the semantics of public field access via either a method or direct access to a field, whichever is appropriate for the task.
The art of good object-oriented design includes defining minimal, clear, and cohesive public abstractions.
There are two kinds of “users” of a type: derived types and code that works with instances of the type. Derived types usually need more access to the members of their parent types than users of instances do.
Scala’s visibility rules are similar to Java’s, but tend to be both more consistently applied and more flexible. For example, in Java, if an inner class has a private
member, the enclosing class can see it. In Scala, the enclosing class can’t see a private
member, but Scala provides another way to declare it visible to the enclosing class.
You could also mention here, that an inner class can access private members of it's outer/enclosing class.
Thanks. Added this to the table below.
As in Java and C#, the keywords that modify visibility, such as private
and protected
, appear at the beginning of declarations. You’ll find them before the class
or trait
keywords for types, before the val
or var
for fields, and before the def
for methods.
You can also use an access modifier keyword on the primary constructor of a class. Put it after the type name and type parameters, if any, and before the argument list, as in this example: class Restricted[+A] private (name: String) {…}
Table 5.1, “Visibility Scopes.” summarizes the visibility scopes.
wouldn't this suffice, in the note
class Restricted[A] private (name: String) {…}
especially since you have not yet mentioned variance and type parameters.
Table 5.1. Visibility Scopes.
Name | Keyword | Description |
---|---|---|
public | none | Public members and types are visible everywhere, across all boundaries. |
protected |
| Protected members are visible to the defining type, to derived types, and to nested types. Protected types are visible only within the same package and subpackages. |
private |
| Private members are visible only within the defining type and nested types. Private types are visible only within the same package. |
scoped protected |
| Visibility is limited to |
scoped private |
| Synonymous with scoped protected visibility, except under inheritance (discussed below). |
"Private members are visible only within the defining type. Private types are visible only within the same package." Is it possible to define a single type across multiple packages?
Protected types are also visible within the sub packages.
@Alexander: no, that's not possible. @Mario: true. thanks. will fix.
Let’s explore these visibility options in more detail. To keep things simple, we’ll use fields for member examples. Method and type
declarations behave the same way.
The note below is good! The 'non apologizing' for the missing but useful feature of being able to set visibility for packages is IMHo much better than the varieties of "we didn't need that feature often" often found in books about various programming languages.
Unfortunately, you can’t apply any of the visibility modifiers to packages. Therefore, a package is always public, even when it contains no publicly visible types.
Any declaration without a visibility keyword is “public”, meaning it is visible everywhere. There is no public
keyword in Scala. This is in contrast to Java, which defaults to public visibility only within the enclosing package (i.e., “package private”). Other object-oriented languages, like Ruby, also default to public visibility.
// code-examples/BasicOOP/scoping/public.scala package scopeA { class PublicClass1 { val publicField = 1 class Nested { val nestedField = 1 } val nested = new Nested } class PublicClass2 extends PublicClass1 { val field2 = publicField + 1 val nField2 = new Nested().nestedField } } package scopeB { class PublicClass1B extends scopeA.PublicClass1 class UsingClass(val publicClass: scopeA.PublicClass1) { def method = "UsingClass:" + " field: " + publicClass.publicField + " nested field: " + publicClass.nested.nestedField } }
You can compile this file with scalac
. It should compile without error.
Everything is public in these packages and classes. Note that scopeB.UsingClass
can access scopeA.PublicClass1
and its members, including the instance of Nested
and its public field.
Protected visibility is for the benefit of implementers of derived types, who need a little more access to the details of their parent types. Any member declared with the protected
keyword is visible only to the defining type, including other instances of the same type and any derived types. When applied to a type, protected
limits visibility to the enclosing package.
Java, in contrast, makes protected members visible throughout the enclosing package. Scala handles this case with scoped private and protected access.
// code-examples/BasicOOP/scoping/protected-wont-compile.scala // WON'T COMPILE package scopeA { class ProtectedClass1(protected val protectedField1: Int) { protected val protectedField2 = 1 def equalFields(other: ProtectedClass1) = (protectedField1 == other.protectedField1) && (protectedField1 == other.protectedField1) && (nested == other.nested) class Nested { protected val nestedField = 1 } protected val nested = new Nested } class ProtectedClass2 extends ProtectedClass1(1) { val field1 = protectedField1 val field2 = protectedField2 val nField = new Nested().nestedField // ERROR } class ProtectedClass3 { val protectedClass1 = new ProtectedClass1(1) val protectedField1 = protectedClass1.protectedField1 // ERROR val protectedField2 = protectedClass1.protectedField2 // ERROR val protectedNField = protectedClass1.nested.nestedField // ERROR } protected class ProtectedClass4 class ProtectedClass5 extends ProtectedClass4 protected class ProtectedClass6 extends ProtectedClass4 } package scopeB { class ProtectedClass4B extends scopeA.ProtectedClass4 // ERROR }
When you compile this file with scalac
, you get the following output. (The file name before the N:
line numbers have been removed from the output to better fit the space.)
in: def equalFields(other: ProtectedClass1) = (protectedField1 == other.protectedField1) && (protectedField1 == other.protectedField1) && (nested == other.nested) there is a typing error should be def equalFields(other: ProtectedClass1) = (protectedField1 == other.protectedField1) && (protectedField2 == other.protectedField2) && (nested == other.nested)
16: error: value nestedField cannot be accessed in ProtectedClass2.this.Nested val nField = new Nested().nestedField ^ 20: error: value protectedField1 cannot be accessed in scopeA.ProtectedClass1 val protectedField1 = protectedClass1.protectedField1 ^ 21: error: value protectedField2 cannot be accessed in scopeA.ProtectedClass1 val protectedField2 = protectedClass1.protectedField2 ^ 22: error: value nested cannot be accessed in scopeA.ProtectedClass1 val protectedNField = protectedClass1.nested.nestedField ^ 32: error: class ProtectedClass4 cannot be accessed in package scopeA class ProtectedClass4B extends scopeA.ProtectedClass4 ^ 5 errors found
The // ERROR
comments in the listing mark the lines that fail to parse.
ProtectedClass2
can access protected members of ProtectedClass1
since it derives from it. However, it can’t access the protected nestedField
in protectedClass1.nested
. Also, ProtectedClass3
can’t access protected members of the ProtectedClass1
instance it uses.
Finally, because ProtectedClass4
is declared protected
, it is not visible in the scopeB
package.
Private visibility completely hides implementation details, even from the implementers of derived classes. Any member declared with the private
keyword is visible only to the defining type, including other instances of the same type. When applied to a type, private
limits visibility to the enclosing package.
// code-examples/BasicOOP/scoping/private-wont-compile.scala // WON'T COMPILE package scopeA { class PrivateClass1(private val privateField1: Int) { private val privateField2 = 1 def equalFields(other: PrivateClass1) = (privateField1 == other.privateField1) && (privateField2 == other.privateField2) && (nested == other.nested) class Nested { private val nestedField = 1 } private val nested = new Nested } class PrivateClass2 extends PrivateClass1(1) { val field1 = privateField1 // ERROR val field2 = privateField2 // ERROR val nField = new Nested().nestedField // ERROR } class PrivateClass3 { val privateClass1 = new PrivateClass1(1) val privateField1 = privateClass1.privateField1 // ERROR val privateField2 = privateClass1.privateField2 // ERROR val privateNField = privateClass1.nested.nestedField // ERROR } private class PrivateClass4 class PrivateClass5 extends PrivateClass4 // ERROR protected class PrivateClass6 extends PrivateClass4 // ERROR private class PrivateClass7 extends PrivateClass4 } package scopeB { class PrivateClass4B extends scopeA.PrivateClass4 // ERROR }
Compiling this file yields the following output.
14: error: not found: value privateField1 val field1 = privateField1 ^ 15: error: not found: value privateField2 val field2 = privateField2 ^ 16: error: value nestedField cannot be accessed in PrivateClass2.this.Nested val nField = new Nested().nestedField ^ 20: error: value privateField1 cannot be accessed in scopeA.PrivateClass1 val privateField1 = privateClass1.privateField1 ^ 21: error: value privateField2 cannot be accessed in scopeA.PrivateClass1 val privateField2 = privateClass1.privateField2 ^ 22: error: value nested cannot be accessed in scopeA.PrivateClass1 val privateNField = privateClass1.nested.nestedField ^ 27: error: private class PrivateClass4 escapes its defining scope as part of type scopeA.PrivateClass4 class PrivateClass5 extends PrivateClass4 ^ 28: error: private class PrivateClass4 escapes its defining scope as part of type scopeA.PrivateClass4 protected class PrivateClass6 extends PrivateClass4 ^ 33: error: class PrivateClass4 cannot be accessed in package scopeA class PrivateClass4B extends scopeA.PrivateClass4 ^ 9 errors found
Now, PrivateClass2
can’t access private members of its parent class PrivateClass1
. They are completely invisible to the subclass, as indicated by the error messages. Nor can it access a private field in a Nested
class.
Just as for the case of protected
access, PrivateClass3
can’t access private members of the PrivateClass1
instance it is using. Note, however, that the equalFields
method can access private members of the other
instance.
The declarations of PrivateClass5
and PrivateClass6
fail, because if allowed, they would enable PrivateClass4
to “escape its defining scope”. However, the declaration of PrivateClass7
succeeds, because it is also declared to be private. Curiously, our previous example was able to declare a public class that subclassed a protected class without a similar error.
Finally, just as for protected
type declarations, the private
types can’t be subclassed outside the same package.
Scala allows you to fine-tune the scope of visibility with the scoped private
and protected
visibility declarations. Note that using private
or protected
in a scoped declaration is interchangeable, as they behave identically, except under inheritance when applied to members.
While either choice behaves the same in most scenarios, it is more common to see private[X]
rather than protected[X]
used in code. In the core libraries included with Scala, the ratio is roughly five to one.
Let’s begin by considering the only differences in behavior between scoped private and scoped public, how they behave under inheritance when members have these scopes.
Let’s begin by considering the only differences in behavior between scoped private and scoped public
Probably meant
Let’s begin by considering the only differences in behavior between scoped private and scoped protected
// code-examples/BasicOOP/scoping/scope-inheritance-wont-compile.scala // WON'T COMPILE package scopeA { class Class1 { private[scopeA] val scopeA_privateField = 1 protected[scopeA] val scopeA_protectedField = 2 private[Class1] val class1_privateField = 3 protected[Class1] val class1_protectedField = 4 private[this] val this_privateField = 5 protected[this] val this_protectedField = 6 } class Class2 extends Class1 { val field1 = scopeA_privateField val field2 = scopeA_protectedField val field3 = class1_privateField // ERROR val field4 = class1_protectedField val field5 = this_privateField // ERROR val field6 = this_protectedField } } package scopeB { class Class2B extends scopeA.Class1 { val field1 = scopeA_privateField // ERROR val field2 = scopeA_protectedField val field3 = class1_privateField // ERROR val field4 = class1_protectedField val field5 = this_privateField // ERROR val field6 = this_protectedField } }
Compiling this file yields the following output.
17: error: not found: value class1_privateField val field3 = class1_privateField // ERROR ^ 19: error: not found: value this_privateField val field5 = this_privateField // ERROR ^ 26: error: not found: value scopeA_privateField val field1 = scopeA_privateField // ERROR ^ 28: error: not found: value class1_privateField val field3 = class1_privateField // ERROR ^ 30: error: not found: value this_privateField val field5 = this_privateField // ERROR ^ 5 errors found
The first two errors, inside Class2
, show us that a derived class inside the same package can’t reference a member that is scoped private to the parent class or this
, but it can reference a private member scoped to the package (or type) that encloses both Class1
and Class2
.
In contrast, for a derived class outside the same package, it has no access to any of the scoped private members of Class1
.
However, all the scoped protected members are visible in both derived classes.
We’ll use scoped private declarations for the rest of our examples and discussion, since use of scoped private is a little more common in the Scala library than scoped protected, when the previous inheritance scenarios aren’t a factor.
First, let’s start with the most restrictive visibility, private[this]
, as it affects type members.
// code-examples/BasicOOP/scoping/private-this-wont-compile.scala // WON'T COMPILE package scopeA { class PrivateClass1(private[this] val privateField1: Int) { private[this] val privateField2 = 1 def equalFields(other: PrivateClass1) = (privateField1 == other.privateField1) && // ERROR (privateField2 == other.privateField2) && (nested == other.nested) class Nested { private[this] val nestedField = 1 } private[this] val nested = new Nested } class PrivateClass2 extends PrivateClass1(1) { val field1 = privateField1 // ERROR val field2 = privateField2 // ERROR val nField = new Nested().nestedField // ERROR } class PrivateClass3 { val privateClass1 = new PrivateClass1(1) val privateField1 = privateClass1.privateField1 // ERROR val privateField2 = privateClass1.privateField2 // ERROR val privateNField = privateClass1.nested.nestedField // ERROR } }
Compiling this file yields the following output.
5: error: value privateField1 is not a member of scopeA.PrivateClass1 (privateField1 == other.privateField1) && ^ 14: error: not found: value privateField1 val field1 = privateField1 ^ 15: error: not found: value privateField2 val field2 = privateField2 ^ 16: error: value nestedField is not a member of PrivateClass2.this.Nested val nField = new Nested().nestedField ^ 20: error: value privateField1 is not a member of scopeA.PrivateClass1 val privateField1 = privateClass1.privateField1 ^ 21: error: value privateField2 is not a member of scopeA.PrivateClass1 val privateField2 = privateClass1.privateField2 ^ 22: error: value nested is not a member of scopeA.PrivateClass1 val privateNField = privateClass1.nested.nestedField ^ 7 errors found
Lines 6-8 also won’t parse. Since they are part of the expression that started on line 5, the compiler stopped after the first error.
The private[this]
members are only visible to the same instance. An instance of the same class can’t see private[this]
members of another instance, so the equalFields
method won’t parse.
Otherwise, the visibility of class members is the same as private
without a scope specifier.
When declaring a type with private[this]
, use of this
effectively binds to the enclosing package, as shown here.
// code-examples/BasicOOP/scoping/private-this-pkg-wont-compile.scala // WON'T COMPILE package scopeA { private[this] class PrivateClass1 package scopeA2 { private[this] class PrivateClass2 } class PrivateClass3 extends PrivateClass1 // ERROR protected class PrivateClass4 extends PrivateClass1 // ERROR private class PrivateClass5 extends PrivateClass1 private[this] class PrivateClass6 extends PrivateClass1 private[this] class PrivateClass7 extends scopeA2.PrivateClass2 // ERROR } package scopeB { class PrivateClass1B extends scopeA.PrivateClass1 // ERROR }
Compiling this file yields the following output.
8: error: private class PrivateClass1 escapes its defining scope as part of type scopeA.PrivateClass1 class PrivateClass3 extends PrivateClass1 ^ 9: error: private class PrivateClass1 escapes its defining scope as part of type scopeA.PrivateClass1 protected class PrivateClass4 extends PrivateClass1 ^ 13: error: type PrivateClass2 is not a member of package scopeA.scopeA2 private[this] class PrivateClass7 extends scopeA2.PrivateClass2 ^ 17: error: type PrivateClass1 is not a member of package scopeA class PrivateClass1B extends scopeA.PrivateClass1 ^ four errors found
In the same package, attempting to declare a public
or protected
subclass fails. Only private
and private[this]
subclasses are allowed. Also, PrivateClass2
is scoped to scopeA2
, so you can’t declare it outside scopeA2
. Similarly an attempt to declare a class in unrelated scopeB
using PrivateClass1
also fails.
Hence, when applied to types, private[this]
is equivalent to Java’s package private
visibility.
s/package/private/
Thanks!
Next, let’s examine type-level visibility, private[T]
, where T
is a type.
// code-examples/BasicOOP/scoping/private-type-wont-compile.scala // WON'T COMPILE package scopeA { class PrivateClass1(private[PrivateClass1] val privateField1: Int) { private[PrivateClass1] val privateField2 = 1 def equalFields(other: PrivateClass1) = (privateField1 == other.privateField1) && (privateField2 == other.privateField2) && (nested == other.nested) class Nested { private[Nested] val nestedField = 1 } private[PrivateClass1] val nested = new Nested val nestedNested = nested.nestedField // ERROR } class PrivateClass2 extends PrivateClass1(1) { val field1 = privateField1 // ERROR val field2 = privateField2 // ERROR val nField = new Nested().nestedField // ERROR } class PrivateClass3 { val privateClass1 = new PrivateClass1(1) val privateField1 = privateClass1.privateField1 // ERROR val privateField2 = privateClass1.privateField2 // ERROR val privateNField = privateClass1.nested.nestedField // ERROR } }
Compiling this file yields the following output.
12: error: value nestedField cannot be accessed in PrivateClass1.this.Nested val nestedNested = nested.nestedField ^ 15: error: not found: value privateField1 val field1 = privateField1 ^ 16: error: not found: value privateField2 val field2 = privateField2 ^ 17: error: value nestedField cannot be accessed in PrivateClass2.this.Nested val nField = new Nested().nestedField ^ 21: error: value privateField1 cannot be accessed in scopeA.PrivateClass1 val privateField1 = privateClass1.privateField1 ^ 22: error: value privateField2 cannot be accessed in scopeA.PrivateClass1 val privateField2 = privateClass1.privateField2 ^ 23: error: value nested cannot be accessed in scopeA.PrivateClass1 val privateNField = privateClass1.nested.nestedField ^ 7 errors found
A private[PrivateClass1]
member is visible to other instances, so the equalFields
method now parses. Hence, private[T]
is not as restrictive as private[this]
. Note that PrivateClass1
can’t see Nested.nestedField
, because that field is declared private[Nested]
.
When members of T
are declared private[T]
the behavior is equivalent to private
. It is not equivalent to private[this]
, which is more restrictive.
What if we change the scope of Nested.nestedField
to be private[PrivateClass1]
? Let’s see see how private[T]
affects nested types.
// code-examples/BasicOOP/scoping/private-type-nested-wont-compile.scala // WON'T COMPILE package scopeA { class PrivateClass1 { class Nested { private[PrivateClass1] val nestedField = 1 } private[PrivateClass1] val nested = new Nested val nestedNested = nested.nestedField } class PrivateClass2 extends PrivateClass1 { val nField = new Nested().nestedField // ERROR } class PrivateClass3 { val privateClass1 = new PrivateClass1 val privateNField = privateClass1.nested.nestedField // ERROR } }
Compiling this file yields the following output.
10: error: value nestedField cannot be accessed in PrivateClass2.this.Nested def nField = new Nested().nestedField ^ 14: error: value nested cannot be accessed in scopeA.PrivateClass1 val privateNField = privateClass1.nested.nestedField ^ two errors found
Now nestedField
is visible to PrivateClass1
, but it is still invisible outside of PrivateClass1
. This is how private
works in Java.
Let’s examine scoping using a package name.
// code-examples/BasicOOP/scoping/private-pkg-type-wont-compile.scala // WON'T COMPILE package scopeA { private[scopeA] class PrivateClass1 package scopeA2 { private [scopeA2] class PrivateClass2 private [scopeA] class PrivateClass3 } class PrivateClass4 extends PrivateClass1 protected class PrivateClass5 extends PrivateClass1 private class PrivateClass6 extends PrivateClass1 private[this] class PrivateClass7 extends PrivateClass1 private[this] class PrivateClass8 extends scopeA2.PrivateClass2 // ERROR private[this] class PrivateClass9 extends scopeA2.PrivateClass3 } package scopeB { class PrivateClass1B extends scopeA.PrivateClass1 // ERROR }
Compiling this file yields the following output.
14: error: class PrivateClass2 cannot be accessed in package scopeA.scopeA2 private[this] class PrivateClass8 extends scopeA2.PrivateClass2 ^ 19: error: class PrivateClass1 cannot be accessed in package scopeA class PrivateClass1B extends scopeA.PrivateClass1 ^ two errors found
Note that PrivateClass2
can’t be subclassed outside of scopeA2
, but PrivateClass3
can be subclassed in scopeA
, because it is declared private[scopeA]
.
Finally, let’s look at the effect of package-level scoping of type members.
// code-examples/BasicOOP/scoping/private-pkg-wont-compile.scala // WON'T COMPILE package scopeA { class PrivateClass1 { private[scopeA] val privateField = 1 class Nested { private[scopeA] val nestedField = 1 } private[scopeA] val nested = new Nested } class PrivateClass2 extends PrivateClass1 { val field = privateField val nField = new Nested().nestedField } class PrivateClass3 { val privateClass1 = new PrivateClass1 val privateField = privateClass1.privateField val privateNField = privateClass1.nested.nestedField } package scopeA2 { class PrivateClass4 { private[scopeA2] val field1 = 1 private[scopeA] val field2 = 2 } } class PrivateClass5 { val privateClass4 = new scopeA2.PrivateClass4 val field1 = privateClass4.field1 // ERROR val field2 = privateClass4.field2 } } package scopeB { class PrivateClass1B extends scopeA.PrivateClass1 { val field1 = privateField // ERROR val privateClass1 = new scopeA.PrivateClass1 val field2 = privateClass1.privateField // ERROR } }
Compiling this file yields the following output.
28: error: value field1 cannot be accessed in scopeA.scopeA2.PrivateClass4 val field1 = privateClass4.field1 ^ 35: error: not found: value privateField val field1 = privateField ^ 37: error: value privateField cannot be accessed in scopeA.PrivateClass1 val field2 = privateClass1.privateField ^ three errors found
The only errors are when we attempt to access members scoped to scopeA
from the unrelated package scopeB
and when we attempt to access a member from a nested package scopeA2
that is scoped to that package.
s/package/private/ (below)
Thanks again for the catch.
When a type or member is declared private[P]
, where P
is the enclosing package, then it is equivalent to Java’s package private
visibility.
Scala visibility declarations are very flexible and they behave consistently. They provide fine-grained control over visibility at all possible scopes, from the instance level (private[this]
) up to package-level visibility (private[P]
, for a package P
). For example, they make it easier to create “components” with types exposed outside of the component’s top-level package, while hiding implementation types and type members within the “component’s” packages.
Finally, we have observed a potential “gotcha” with hidden members of traits.
Be careful when choosing the names of members of traits. If two traits have a member of the same name and the traits are used in the same instance, a name collision will occur even if both members are private.
Fortunately, the compiler catches this problem.
We introduced the basics of Scala’s object model, including constructors, inheritance, nesting of classes, and rules for visibility.
In the next chapter we’ll explore Scala’s more advanced OOP features, including overriding, companion objects, case classes, and rules for equality between objects.
No comments yet
Add a comment