In this chapter we’ll look at:
object
keyword.Let’s begin by looking at how we create fields within classes without defining them on the class definition line. If you were to create a class in Scala with no fields defined on the class definition, like this:
// scala
class
Counter
…the Scala compiler would still generate a primary constructor with no arguments, a lot like Java’s default constructor. So the Java equivalent would look like this:
// java
public
class
Counter
{
public
Counter
()
{
}
}
In Java you might initialise a variable and create some methods.
// java
public
class
Counter
{
private
int
count
=
0
;
public
Counter
()
{
}
public
void
increment
()
{
count
++;
}
public
int
getCount
()
{
return
count
;
}
}
You can do the same in Scala.
// scala
class
Counter
{
private
var
count
=
0
def
increment
()
{
// brackets to denote this is a "mutator"
count
+=
1
}
def
getCount
=
count
}
Within the primary constructor (i.e., not in the class definition but immediately afterwards in the class body), the val
and var
keywords will affect the bytecode like this:
Declared in primary constructor | val x | var x | x | private val x | private var x |
---|---|---|---|---|---|
Getter (x() ) | Y (public ) | Y (public ) | N/A | Y (private ) | Y (private ) |
Setter (x_=(y) ) | N | Y (public ) | N/A | N | Y (private ) |
As you can see, this is consistent with the table we saw earlier. Getters are generated by default for val
and var
types and will all be public. Adding private
to the field declaration will make the generated fields private and setters are only generated for vars (which are, again, public by default).
Let’s create an alternative Java version of our Customer
class, this time with additional constructors.
// java
public
class
Customer
{
private
final
String
fullname
;
public
Customer
(
String
forename
,
String
initial
,
String
surname
)
{
this
.
fullname
=
String
.
format
(
"%s %s. %s"
,
forename
,
initial
,
surname
);
}
public
Customer
(
String
forename
,
String
surname
)
{
this
(
forename
,
""
,
surname
);
}
}
We’ve defaulted the customer’s initial and allowed clients to choose if they want to supply it.
We should probably tidy up the main constructor to reflect the fact that the variable could come through as an empty string. We’ll add an if-condition and format the string depending on the result.
// java
public
class
Customer
{
private
final
String
fullname
;
public
Customer
(
String
forename
,
String
initial
,
String
surname
)
{
if
(
initial
!=
null
&&
!
initial
.
isEmpty
())
this
.
fullname
=
String
.
format
(
"%s %s. %s"
,
forename
,
initial
,
surname
);
else
this
.
fullname
=
String
.
format
(
"%s %s"
,
forename
,
surname
);
}
public
Customer
(
String
forename
,
String
surname
)
{
this
(
forename
,
""
,
surname
);
}
public
static
void
main
(
String
...
args
)
{
System
.
out
.
println
(
new
Customer
(
"Bob"
,
"J"
,
"Smith"
).
fullname
);
System
.
out
.
println
(
new
Customer
(
"Bob"
,
"Smith"
).
fullname
);
}
}
Creating additional or auxiliary constructors in Scala is just a matter of creating methods called this
. The one constraint is that each auxiliary constructor must call another constructor using this
on its first line. That way, constructors will always be chained, all the way to the top.
Scala has the notion of a primary constructor; it’s the code in the class body. Any parameters passed in from the class definition are available to it and if you don’t write any auxiliary constructors, the class will still have a constructor; it’s the implicit primary constructor.
// scala
class
Customer
(
forename
:
String
,
initial
:
String
,
surname
:
String
)
{
// primary constructor
}
So, if we create a field within the primary constructor and assign it some value,
// scala
class
Customer
(
forename
:
String
,
initial
:
String
,
surname
:
String
)
{
val
fullname
=
String
.
format
(
"%s %s. %s"
,
forename
,
initial
,
surname
)
}
…it would be equivalent to the following Java:
// java
public
class
Customer
{
private
final
String
fullname
;
public
Customer
(
String
forename
,
String
initial
,
String
surname
)
{
this
.
fullname
=
String
.
format
(
"%s %s. %s"
,
forename
,
initial
,
surname
);
}
}
If we can add an another auxiliary constructor to the Scala version, we can refer to this
to chain the call to the primary constructor.
// scala
class
Customer
(
forename
:
String
,
initial
:
String
,
surname
:
String
)
{
val
fullname
=
String
.
format
(
"%s %s. %s"
,
forename
,
initial
,
surname
)
def
this
(
forename
:
String
,
surname
:
String
)
{
this
(
forename
,
""
,
surname
)
}
}
Scala has language support for default values on method signatures, so we could have written this using just parameters on the class definition, and avoided the extra constructor. We’d just default the value for initial
to be an empty string. To make the implementation handle empty strings better, we can put some logic in the primary constructor like before.
class
Customer
(
forename
:
String
,
initial
:
String
=
""
,
surname
:
String
)
{
val
fullname
=
if
(
initial
!=
null
&&
!
initial
.
isEmpty
)
forename
+
" "
+
initial
+
". "
+
surname
else
forename
+
" "
+
surname
}
When calling it, we may need to name default values; for example:
new
Customer
(
"Bob"
,
"J"
,
"Smith"
)
"Bob", "J", "Smith"
is ok, but if we skip the initial
variable, we’d need to name the surname variable like this:
new
Customer
(
"Bob"
,
surname
=
"Smith"
)
In Java you can enforce a single instance of a class using the singleton pattern. Scala has made this idea as a feature of the language: as well as classes, you can define (singleton) objects.
The downside is that when we talk about “objects” in Scala, we’re overloading the term. We might mean an instance of a class (for example, a new ShoppingCart()
, of which there could be many) or we might mean the one and only instance of a class; that is, a singleton object.
A typical use-case for a singleton in Java is if we need to use a single logger instance across an entire application.
// java
Logger
.
getLogger
(
"example"
).
log
(
INFO
,
"Everything is fine."
);
We might implement the singleton like this:
// java
public
final
class
Logger
{
private
static
final
Logger
INSTANCE
=
new
Logger
();
private
Logger
()
{
}
public
static
Logger
getLogger
()
{
return
INSTANCE
;
}
public
void
log
(
Level
level
,
String
string
)
{
System
.
out
.
printf
(
"%s %s%n"
,
level
,
string
);
}
}
We create a Logger
class, and a single static instance of it. We prevent anyone else creating one by using a private constructor. We then create an accessor to the static instance, and finally give it a rudimentary log method. We’d call it like this:
// java
Logger
.
getLogger
().
log
(
INFO
,
"Singleton loggers say YEAH!"
);
A more concise way to achieve the same thing in Java would be to use an enum.
// java
public
enum
LoggerEnum
{
LOGGER
;
public
void
log
(
Level
level
,
String
string
)
{
System
.
out
.
printf
(
"%s %s%n"
,
level
,
string
);
}
}
We don’t need to use an accessor method; Java ensures a single instance is used and we’d call it like this:
// java
LOGGER
.
log
(
INFO
,
"An alternative example using an enum"
);
Either way, they prevent clients newing up an instance of the class and provide a single, global instance for use.
The Scala equivalent would look like this:
// scala
object
Logger
{
def
log
(
level
:
Level
,
string
:
String
)
{
printf
(
"%s %s%n"
,
level
,
string
)
}
}
The thing to notice here is that the singleton instance is denoted by the object
keyword rather than class
. So we’re saying “define a single object called Logger
” rather than “define a class”.
Under the covers, Scala is creating basically the same Java code as our singleton pattern example. You can see this when we decompile it.
1
// decompiled from scala to java
2
public
final
class
Logger
$
{
3
public
static
final
Logger$
MODULE$
;
4
5
public
static
{
6
new
scala
.
demo
.
singleton
.
Logger
$
();
7
}
8
9
public
void
log
(
Level
level
,
String
string
)
{
10
Predef
..
MODULE
$
.
printf
(
"%s %s%n"
,
(
Seq
)
Predef
..
MODULE
$
11
.
genericWrapArray
((
Object
)
new
Object
[]{
level
,
string
}));
12
}
13
14
private
Logger$
()
{
15
Logger$
.
MODULE
$
=
this
;
16
}
17
}
There are some oddities in the log
method, but that’s the decompiler struggling to decompile the bytecode, and generally how Scala goes about things. In essence though, it’s equivalent; there’s a private constructor like the Java version, and a single static instance of the object. The class itself is even final.
There’s no need to new up a new Logger
; Logger
is already an object, so we can refer to it directly. In fact, you couldn’t new one up if you wanted to, because there’s no class definition and so no class to new up.
Incidentally, you replicate Java’s static main
method by adding a main
method to a Scala singleton object, not a class.
You can combine objects and classes in Scala. When you create a class and an object with the same name in the same source file, the object is known as a companion object.
Scala doesn’t have a static
keyword but members of singleton objects are effectively static. Remember that a Scala singleton object is just that, a singleton. Any members it contains will therefore be reused by all clients using the object; they’re globally available just like statics.
You use companion objects where you would mix statics and non-statics in Java.
The Java version of Customer
has fields for the customer’s name and address, and an ID to identify the customer uniquely.
// java
public
class
Customer
{
private
final
String
name
;
private
final
String
address
;
private
Integer
id
;
public
Customer
(
String
name
,
String
address
)
{
this
.
name
=
name
;
this
.
address
=
address
;
}
}
Now we may want to create a helper method to create the next ID in a sequence. To do that globally, we create a static field to capture a value for the ID and a method to return and increment it. We can then just call the method on construction of a new instance, assigning its ID to the freshly incremented global ID.
// java
public
class
Customer
{
private
static
Integer
sequenceOfIds
;
private
final
String
name
;
private
final
String
address
;
private
Integer
id
;
public
Customer
(
String
name
,
String
address
)
{
this
.
name
=
name
;
this
.
address
=
address
;
this
.
id
=
Customer
.
nextId
();
}
private
static
Integer
nextId
()
{
return
sequenceOfIds
++;
}
}
It’s static because we want to share its implementation among all instances to create unique IDs for each.
In Scala, we’d separate the static from non-static members and put the statics in the singleton object and the rest in the class. The singleton object is the companion object to Customer
.
We create our class with the two required fields and in the singleton object, create the nextId
method. Next we create a private var
to capture the current value, assigning it the value of zero so Scala can infer the type as an Integer
. Adding a val
here means no setter will be generated, and adding the private
modifier means the generated getter will be private. We finish off by implementing the increment in the nextId
method and calling it from the primary constructor.
// scala
class
Customer
(
val
name
:
String
,
val
address
:
String
)
{
private
val
id
=
Customer
.
nextId
()
}
object
Customer
{
private
var
sequenceOfIds
=
0
private
def
nextId
()
:
Integer
=
{
sequenceOfIds
+=
1
sequenceOfIds
}
}
The singleton object is a companion object because it has the same name and lives in the same source file as its class. This means the two have a special relationship and can access each other’s private members. That’s how the Customer
object can define the nextId
method as private but the Customer
class can still access it.
If you were to name the object differently, you wouldn’t have this special relationship and wouldn’t be able to call the method. For example, the class CustomerX
object below is not a companion object to Customer
and so can’t see the private nextId
method.
// scala
class
Customer
(
val
name
:
String
,
val
address
:
String
)
{
private
val
id
=
CustomerX
.
nextId
()
// compiler failure
}
object
CustomerX
{
private
var
sequenceOfIds
=
0
private
def
nextId
()
:
Integer
=
{
sequenceOfIds
+=
1
sequenceOfIds
}
}
When methods don’t depend on any of the fields in a class, you can more accurately think of them as functions. Functions generally belong in a singleton object rather than a class, so one example of when to use companion objects is when you want to distinguish between functions and methods, but keep related functions close to the class they relate to.
Another reason to use a companion object is for factory-style methods — methods that create instances of the class companion. For example, you might want to create a factory method that creates an instance of your class but with less noise. If we want to create a factory for Customer
, we can do so like this:
// scala
class
Customer
(
val
name
:
String
,
val
address
:
String
)
{
val
id
=
Customer
.
nextId
()
}
object
Customer
{
def
apply
(
name
:
String
,
address
:
String
)
=
new
Customer
(
name
,
address
)
}
The apply
method affords a shorthand notation for a class or object. It’s kind of like the default method for a class, so if you don’t call a method directly on an instance, but instead match the arguments of an apply
method, it’ll call it for you. For example, you can call:
Customer
.
apply
(
"Bob Fossil"
,
"1 London Road"
)
…or you can drop the apply
and Scala will look for an apply method that matches your argument. The two are identical.
Customer
(
"Bob Fossil"
,
"1 London Road"
)
You can still construct a class using the primary constructor and new
, but implementing the companion class apply
method as a factory means you can be more concise if you have to create a lot of objects.
You can even force clients to use your factory method rather than the constructor by making the primary constructor private.
class
Customer
private
(
val
name
:
String
,
val
address
:
String
)
{
val
id
=
Customer
.
nextId
()
}
The Java analog would have a static factory method, for example createCustomer
, and a private constructor ensuring everyone is forced to use the factory method.
// java
public
class
Customer
{
private
static
Integer
sequenceOfIds
;
private
final
String
name
;
private
final
String
address
;
private
Integer
id
;
public
static
Customer
createCustomer
(
String
name
,
String
address
)
{
return
new
Customer
(
name
,
address
);
}
private
Customer
(
String
name
,
String
address
)
{
this
.
name
=
name
;
this
.
address
=
address
;
this
.
id
=
Customer
.
nextId
();
}
private
static
Integer
nextId
()
{
return
sequenceOfIds
++;
}
}