4.6. Overriding Default Accessors and Mutators

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.)

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:

  1. 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.

  2. Define getter and setter names that you want other classes to use. In the Solution the getter name is name, and the setter name is name_= (which, combined with Scala’s syntactic sugar, lets users write p.name = "Jony").

  3. 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.

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.