This is an excerpt from the 1st Edition of the Scala Cookbook (partially modified for the internet). This is Recipe 4.6, “How to override default accessors and mutators in Scala classes.”
Problem
You want to override the getter or setter methods that Scala generates for you.
Solution
This is a bit of a trick problem, because you can’t override the getter and setter methods Scala generates for you, at least not if you want to stick with the Scala naming conventions. For instance, if you have a class named Person
with a constructor parameter named name
, and attempt to create getter and setter methods according to the Scala conventions, your code won’t compile:
// error: this won't work class Person(private var name: String) { // this line essentially creates a circular reference def name = name def name_=(aName: String) { name = aName } }
Attempting to compile this code generates three errors:
Person.scala:3: error: overloaded method name needs result type def name = name ^ Person.scala:4: error: ambiguous reference to overloaded definition, both method name_= in class Person of type (aName: String)Unit and method name_= in class Person of type (x$1: String)Unit match argument types (String) def name_=(aName: String) { name = aName } ^ Person.scala:4: error: method name_= is defined twice def name_=(aName: String) { name = aName } ^ three errors found
I’ll examine these problems more in the Discussion, but the short answer is that both the constructor parameter and the getter method are named name
, and Scala won’t allow that.
To solve this problem, change the name of the field you use in the class constructor so it won’t collide with the name of the getter method you want to use. A common approach is to add a leading underscore to the parameter name
, so if you want to manually create a getter method called name
, use the parameter name _name
in the constructor, then declare your getter and setter methods according to the Scala conventions:
class Person(private var _name: String) { def name = _name // accessor def name_=(aName: String) { _name = aName } // mutator }
Notice the constructor parameter is declared private
and var
. The private
keyword keeps Scala from exposing that field to other classes, and the var
lets the value of the field be changed.
Creating a getter method named name
and a setter method named name_=
conforms to the Scala convention and lets a consumer of your class write code like this:
val p = new Person("Jonathan") p.name = "Jony" // setter println(p.name) // getter
If you don’t want to follow this Scala naming convention for getters and setters, you can use any other approach you want. For instance, you can name your methods getName
and setName
, following the JavaBean style. (However, if JavaBeans are what you really want, you may be better off using the @BeanProperty annotation, as described in Recipe 17.6, “When Java Code Requires JavaBeans”.)
Discussion
When you define a constructor parameter to be a var
field, Scala makes the field private to the class and automatically generates getter and setter methods that other classes can use to access the field. For instance, given a simple class like this:
class Stock (var symbol: String)
after the class is compiled with scalac
, you’ll see this signature when you disassemble it with javap
:
$ javap Stock public class Stock extends java.lang.Object{ public java.lang.String symbol(); public void symbol_$eq(java.lang.String); public Stock(java.lang.String); }
You can see that the Scala compiler generated two methods: a getter named symbol
and a setter named symbol_$eq
. This second method is the same as a method you’d name symbol_=
, but Scala needs to translate the =
symbol to $eq
to work with the JVM.
That second method name is a little unusual, but it follows a Scala convention, and when it’s mixed with some syntactic sugar, it lets you set the symbol
field on a Stock instance like this:
stock.symbol = "GOOG"
The way this works is that behind the scenes, Scala converts that line of code into this line of code:
stock.symbol_$eq("GOOG")
You generally never have to think about this, unless you want to override the mutator method.
Summary
As shown in the Solution, the recipe for overriding default getter and setter methods is:
- Create a private
var
constructor parameter with a name you want to reference from within your class. In the example in the Solution, the field is named_name
. - Define getter and setter names that you want other classes to use. In the Solution the getter name is
name
, and the setter name isname_=
(which, combined with Scala’s syntactic sugar, lets users writep.name = "Jony"
). - Modify the body of the getter and setter methods as desired.
It’s important to remember the private
setting on your field. If you forget to control the access with private
(or private[this]
), you’ll end up with getter/setter methods for the field you meant to hide. For example, in the following code, I intentionally left the private
modifier off of the _symbol
constructor parameter:
// intentionally left the 'private' modifier off _symbol class Stock (var _symbol: String) { // getter def symbol = _symbol // setter def symbol_= (s: String) { this.symbol = s println(s"symbol was updated, new value is $symbol") } }
Compiling and disassembling this code shows the following class signature, including two methods I “accidentally” made visible:
public class Stock extends java.lang.Object{ public java.lang.String _symbol(); // error public void _symbol_$eq(java.lang.String); // error public java.lang.String symbol(); public void symbol_$eq(java.lang.String); public Stock(java.lang.String); }
Correctly adding private to the _symbol
field results in the correct signature in the disassembled code:
public class Stock extends java.lang.Object{ public java.lang.String symbol(); // println(stock.symbol) public void symbol_$eq(java.lang.String); // stock.symbol = "AAPL" public Stock(java.lang.String); }
Note that while these examples used fields in a class constructor, the same principles hold true for fields defined inside a class.
this post is sponsored by my books: | |||
#1 New Release |
FP Best Seller |
Learn Scala 3 |
Learn FP Fast |