You want to define abstract or concrete properties in an abstract base class (or trait) that can be referenced in all child classes.
You can declare both val
and
var
fields in an abstract class (or
trait), and those fields can be abstract or have concrete
implementations. All of these variations are shown in this
recipe.
The following example demonstrates an Animal
trait with abstract val
and var
fields, along with a simple concrete
method named sayHello
, and an
override of the toString
method:
abstract
class
Pet
(
name
:
String
)
{
val
greeting
:
String
var
age
:
Int
def
sayHello
{
println
(
greeting
)
}
override
def
toString
=
s
"I say $greeting, and I'm $age"
}
The following Dog
and
Cat
classes extend the Animal
class and provide values for the
greeting
and age
fields. Notice that the fields are again
specified as val
or var
:
class
Dog
(
name
:
String
)
extends
Pet
(
name
)
{
val
greeting
=
"Woof"
var
age
=
2
}
class
Cat
(
name
:
String
)
extends
Pet
(
name
)
{
val
greeting
=
"Meow"
var
age
=
5
}
The functionality can be demonstrated with a simple driver object:
object
AbstractFieldsDemo
extends
App
{
val
dog
=
new
Dog
(
"Fido"
)
val
cat
=
new
Cat
(
"Morris"
)
dog
.
sayHello
cat
.
sayHello
println
(
dog
)
println
(
cat
)
// verify that the age can be changed
cat
.
age
=
10
println
(
cat
)
}
The resulting output looks like this:
Woof Meow I say Woof, and I'm 2 I say Meow, and I'm 5 I say Meow, and I'm 10
Concrete field implementations are presented in the Discussion, because it helps to understand how the Scala compiler translates your code in the preceding examples.
As shown, you can declare abstract fields in an abstract class as
either val
or var
, depending on your needs. The way abstract
fields work in abstract classes (or traits) is interesting:
An abstract
var
field results in getter and setter methods being generated for the field.An abstract
val
field results in a getter method being generated for the field.When you define an abstract field in an abstract class or trait, the Scala compiler does not create a field in the resulting code; it only generates the methods that correspond to the
val
orvar
field.
In the example shown in the Solution, if you look at the code
that’s created by scalac
using the
-Xprint:all
option, or by decompiling
the resulting Pet.class file, you
won’t find greeting
or age
fields. For instance, if you decompile the
class, the output shows only methods in the class, no fields:
import
scala.*
;
import
scala.runtime.BoxesRunTime
;
public
abstract
class
Pet
{
public
abstract
String
greeting
();
public
abstract
int
age
();
public
abstract
void
age_$eq
(
int
i
);
public
void
sayHello
()
{
Predef$
.
MODULE
$
.
println
(
greeting
());
}
public
String
toString
(){
// code omitted
}
public
Pet
(
String
name
){}
}
Because of this, when you provide concrete values for these fields
in your concrete classes, you must again define your fields to be
val
or var
. Because the fields don’t actually exist
in the abstract base class (or trait), the override
keyword is not necessary.
As another result of this, you may see developers define a
def
that takes no parameters in the
abstract base class rather than defining a val
. They can then define a val
in the concrete class, if desired. This
technique is demonstrated in the following code:
abstract
class
Pet
(
name
:
String
)
{
def
greeting
:
String
}
class
Dog
(
name
:
String
)
extends
Pet
(
name
)
{
val
greeting
=
"Woof"
}
object
Test
extends
App
{
val
dog
=
new
Dog
(
"Fido"
)
println
(
dog
.
greeting
)
}
Given this background, it’s time to examine the use of concrete
val
and var
fields in abstract classes.
When defining a concrete val
field in an abstract class, you can provide an initial value, and then
override that value in concrete subclasses:
abstract
class
Animal
{
val
greeting
=
"Hello"
// provide an initial value
def
sayHello
{
println
(
greeting
)
}
def
run
}
class
Dog
extends
Animal
{
override
val
greeting
=
"Woof"
// override the value
def
run
{
println
(
"Dog is running"
)
}
}
In this example, the greeting
variable is created in both classes. To demonstrate this, running the
following code:
abstract
class
Animal
{
val
greeting
=
{
println
(
"Animal"
);
"Hello"
}
}
class
Dog
extends
Animal
{
override
val
greeting
=
{
println
(
"Dog"
);
"Woof"
}
}
object
Test
extends
App
{
new
Dog
}
results in this output, showing that both values are created:
Animal Dog
To prove this, you can also decompile both the Animal
and Dog
classes, where you’ll find the greeting
declared like this:
private
final
String
greeting
=
"Hello"
;
To prevent a concrete val
field in an abstract base class from being overridden in a subclass,
declare the field as a final
val
:
abstract
class
Animal
{
final
val
greeting
=
"Hello"
// made the field 'final'
}
class
Dog
extends
Animal
{
val
greeting
=
"Woof"
// this line won't compile
}
You can also give var
fields
an initial value in your trait or abstract class, and then refer to
them in your concrete subclasses, like this:
abstract
class
Animal
{
var
greeting
=
"Hello"
var
age
=
0
override
def
toString
=
s
"I say $greeting, and I'm $age years old."
}
class
Dog
extends
Animal
{
greeting
=
"Woof"
age
=
2
}
In this case, these fields are declared and assigned in the
abstract base class, as shown in the decompiled code for the Animal
class:
private
String
greeting
;
private
int
age
;
public
Animal
(){
greeting
=
"Hello"
;
age
=
0
;
}
// more code ...
Because the fields are declared and initialized in the abstract
Animal
base class, there’s no need
to redeclare the fields as val
or
var
in the concrete Dog
subclass.
You can verify this by looking at the code the Scala compiler
generates for the Dog
class. When
you compile the code with scalac
-Xprint:all
, and look at the last lines of output, you’ll
see how the compiler has converted the Dog
class:
class
Dog
extends
Animal
{
def
<
init
>()
:
Dog
=
{
Dog
.
super
.<
init
>();
Dog
.
this
.
greeting_=
(
"Woof"
);
Dog
.
this
.
age_=
(
2
);
()
}
}
Because the fields are concrete fields in the abstract base
class, they just need to be reassigned in the concrete Dog
class.
As discussed in many recipes in this book, including Recipe 20.5, you shouldn’t use null values in these
situations. If you’re tempted to use a null
, instead initialize the fields using
the
Option
/Some
/None
pattern. The following example demonstrates how to initialize val
and var
fields with this approach:
trait
Animal
{
val
greeting
:
Option
[
String
]
var
age
:
Option
[
Int
]
=
None
override
def
toString
=
s
"I say $greeting, and I'm $age years old."
}
class
Dog
extends
Animal
{
val
greeting
=
Some
(
"Woof"
)
age
=
Some
(
2
)
}
object
Test
extends
App
{
val
d
=
new
Dog
println
(
d
)
}
Running this Test
object
yields the following output:
I say Some(Woof), and I'm Some(2) years old.
See Recipe 5.2, for more examples of how to call methods on superclasses. |
Get Scala Cookbook 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.