In this chapter, we’ll have a look at:
Creating a class in Java means writing something like this:
// java
public
class
Customer
{
}
It makes sense for us to have a name and address for a customer. So adding these as fields and initialising via the constructor would give us something like this:
// java
public
class
Customer
{
private
final
String
name
;
private
final
String
address
;
public
Customer
(
String
name
,
String
address
)
{
this
.
name
=
name
;
this
.
address
=
address
;
}
}
We can instantiate an instance with the new
keyword and create a new customer called Eric like this:
Customer
eric
=
new
Customer
(
"Eric"
,
"29 Acacia Road"
);
// java
In Scala, the syntax is much briefer; we can combine the class and constructor on a single line.
class
Customer
(
val
name
:
String
,
val
address
:
String
)
// scala
We new it up in the same way, like this:
val
eric
=
new
Customer
(
"Eric"
,
"29 Acacia Road"
)
// scala
Rather than define the fields as members within the class, the Scala version declares the variables as part of the class definition in what’s known as the primary constructor. In one line, we’ve declared the class Customer
and, in effect, declared a constructor with two arguments.
The val
keyword on the class definition tells the compiler to treat the arguments as fields. It will create the fields and accessor methods for them.
We can prove this by taking the generated class file and decompiling it into Java. Round-tripping like this is a great way to explore what Scala actually produces behind the scenes. I’ve used the excellent by Lee Benfield here, but you could also use the javap
program that ships with Java to get the basic information.
To run the decompiler on the Scala generated class file for Customer
, you do something like the following:
java
-
jar
cfr_0_99
.
jar
target
/
scala
-
2.11
/
classes
/
scala
/
demo
/
Customer
.
class
It produces the following:
1
// decompiled from scala to java
2
public
class
Customer
{
3
private
final
String
name
;
4
private
final
String
address
;
5
6
public
String
name
()
{
7
return
this
.
name
;
8
}
9
10
public
String
address
()
{
11
return
this
.
address
;
12
}
13
14
public
Customer
(
String
name
,
String
address
)
{
15
this
.
name
=
name
;
16
this
.
address
=
address
;
17
}
18
}
What’s important to notice is that Scala has generated accessor methods at lines 6 and 10, and a constructor at line 14. The accessors aren’t using the Java getter convention, but we’ve got the equivalent of getName
and getAddress
.
You might also want to define fields but not have them set via the constructor. For example, in Java, we might want to add an id
to the customer to be set later with a setter method. This is a common pattern for tools like Hibernate when populating an object from the database.
// java
public
class
Customer
{
private
final
String
name
;
private
final
String
address
;
private
String
id
;
public
Customer
(
String
name
,
String
address
)
{
this
.
name
=
name
;
this
.
address
=
address
;
}
public
void
setId
(
String
id
)
{
this
.
id
=
id
;
}
}
Is Scala, you do pretty much the same thing.
// scala
class
Customer
(
val
name
:
String
,
val
address
:
String
)
{
var
id
=
""
}
You define a field, in this case a var
, and magically Scala will create a setter method for you. The setter method it creates is called id_=
rather than the usual setId
. If we round-trip this through the decompiler, we see the following:
1
// decompiled from scala to java
2
public
class
Customer
{
3
private
final
String
name
;
4
private
final
String
address
;
5
private
String
id
;
6
7
public
static
Customer
apply
()
{
8
return
Customer$
.
MODULE
$
.
apply
();
9
}
10
public
String
name
()
{
11
return
this
.
name
;
12
}
13
public
String
address
()
{
14
return
this
.
address
;
15
}
16
public
String
id
()
{
// notice it's public
17
return
this
.
id
;
18
}
19
public
void
id_$eq
(
String
x$1
)
{
// notice it's public
20
this
.
id
=
x$1
;
21
}
22
public
Customer
(
String
name
,
String
address
)
{
23
this
.
name
=
name
;
24
this
.
address
=
address
;
25
this
.
id
=
null
;
26
}
27
}
Notice it has created a method called id_$eq
on line 19 rather than id_=
; that’s because the equals symbol isn’t allowed in a method name on the JVM, so Scala has escaped it and will translate it as required. You can call the setter method directly like this:
new
Customer
(
"Bob"
,
"10 Downing Street"
).
id_=
(
"000001"
)
Scala offers a shorthand, however; you can just use regular assignment and Scala will call the auto-generated id_$eq
setter method under the covers:
new
Customer
(
"Bob"
,
"10 Downing Street"
).
id
=
"000001"
If there are no modifiers in front of a field, it means it’s public. So as well as being able to call the auto-generated setter, clients could also work directly on the field, potentially breaking encapsulation. We’d like to be able to make the field private and allow updates only from within the Customer
class.
To do this, just use the private
keyword with the field.
class
Customer
(
val
name
:
String
,
val
address
:
String
)
{
private
var
id
=
""
}
The decompiler shows that the setter and getter methods are now private.
1
// decompiled from scala to java
2
public
class
Customer
{
3
private
final
String
name
;
4
private
final
String
address
;
5
private
String
id
;
6
7
public
String
name
()
{
8
return
this
.
name
;
9
}
10
11
public
String
address
()
{
12
return
this
.
address
;
13
}
14
15
private
String
id
()
{
// now it's private
16
return
this
.
id
;
17
}
18
19
private
void
id_$eq
(
String
x$1
)
{
// now it's private
20
this
.
id
=
x$1
;
21
}
22
23
public
Customer
(
String
name
,
String
address
)
{
24
this
.
name
=
name
;
25
this
.
address
=
address
;
26
this
.
id
=
""
;
27
}
28
}
The advantage of using setters to set values is that we can use the method to preserve invariants or perform special processing. In Java, it’s straightforward: you create the setter method in the first place. It’s more laborious for Scala, as the compiler is generating the methods.
For example, once the id
has been set, we might want to prevent it from being updated. In Java, we could do something like this:
// java
public
void
setId
(
String
id
)
{
if
(
id
.
isEmpty
())
this
.
id
=
id
;
}
Scala, on the other hand, creates the setter method automatically, so how do we redefine it? If we try to just replace the setter directly in the Scala code, we’d get a compiler error:
// scala doesn't compile
class
Customer
(
val
name
:
String
,
val
address
:
String
)
{
private
var
id
=
""
def
id_=
(
value
:
String
)
{
if
(
id
.
isEmpty
)
this
.
id
=
value
}
}
Scala can’t know to replace the method so it creates a second method of the same name, and the compiler fails when it sees the duplicate:
ambiguous
reference
to
overloaded
definition
,
both
method
id_=
in
class
Customer
of
type
(
value:
String
)
Unit
and
method
id_=
in
class
Customer
of
type
(
x$1:
String
)
Unit
match
argument
types
(
String
)
this
.
id
=
value
method
id_=
is
defined
twice
conflicting
symbols
both
originated
in
file
'Customer
.
scala
'
def
id_=
(
value
:
String
)
{
^
^
To redefine the method, we have to jump through some hoops. Firstly, we have to rename the field (say to _id
), making it private so as to make the getter and setters private. Then we create a new getter method called id
and setter method called id_=
that are public and are used to access the renamed private field.
class
Customer
(
val
name
:
String
,
val
address
:
String
)
{
private
var
_id
:
String
=
""
def
id
=
_id
def
id_=
(
value
:
String
)
{
if
(
_id
.
isEmpty
)
_id
=
value
}
}
We’ve hidden the real field _id
behind the private
modifier and exposed a method called id_=
to act as a setter. As there is no field called id
any more, Scala won’t try to generate the duplicate method, and things compile.
// REPL session
scala
>
val
bob
=
new
Customer
(
"Bob"
,
"32 Bread Street"
)
bob
:
Customer
=
Customer
@e955027
scala
>
bob
.
id
=
"001"
bob
.
id
:
String
=
001
scala
>
println
(
bob
.
id
)
001
scala
>
bob
.
id
=
"002"
bob
.
id
:
String
=
001
scala
>
println
(
bob
.
id
)
001
Looking at the decompiled version, you can see how to redefine the method. We’ve hidden the real field and exposed public methods to synthesize access to it under the guise of the field name.
1
// decompiled from scala to java
2
public
class
Customer
{
3
private
final
String
name
;
4
private
final
String
address
;
5
private
String
_id
;
6
7
public
String
name
()
{
8
return
this
.
name
;
9
}
10
11
public
String
address
()
{
12
return
this
.
address
;
13
}
14
15
private
String
_id
()
{
// private
16
return
this
.
_id
;
17
}
18
19
private
void
_id_$eq
(
String
x$1
)
{
// private
20
this
.
_id
=
x$1
;
21
}
22
23
public
String
id
()
{
// public
24
return
this
.
_id
();
25
}
26
27
public
void
id_$eq
(
String
value
)
{
// public
28
if
(!
this
.
_id
().
isEmpty
())
return
;
29
this
.
_id_
$eq
(
value
);
30
}
31
32
public
Customer
(
String
name
,
String
address
)
{
33
this
.
name
=
name
;
34
this
.
address
=
address
;
35
this
.
_id
=
""
;
36
}
37
}
Creating classes is straightforward with Scala. You can add fields to the class simply by adding parameters to the class definition, and the equivalent Java constructor, getters and setters are generated for you by the compiler.
All fields in the class file are generated as private but have associated accessor methods generated. These generated methods are affected by the presence of val
or var
in the class definition.
val
is used, a public getter is created but no setter is created. The value can only be set by the constructor.var
is used, a public getter and setter is created. The value can be set via the setter or the constructor.val
or var
is used, no methods are generated and the value can only be used within the scope of the primary constructor; it’s not really a field in this case.private
won’t change these rules, but will make any generated methods private.This is summarised in the following table:
class Foo(? x) | val x | var x | x | private val x | private var x |
---|---|---|---|---|---|
Getter created (x() ) | Y (public ) | Y (public ) | N | Y (private ) | Y (private ) |
Setter created (x_=(y) ) | N | Y (public ) | N | N | Y (private ) |
Generated constructor includes x | Y | Y | N | Y | Y |
If you need to override the generated methods, you have to rename the field and mark it as private. You then recreate the getter and setter methods with the original name. In practice, it’s not something you’ll have to do very often.