Книга: Learn Scala for Java Developers
Назад: II. Key Syntactical Differences
Дальше: Classes and Objects

Classes and Fields

In this chapter, we’ll have a look at:

  1. Creating classes
  2. How Scala makes things easier when defining fields
  3. What happens behind the scenes when Scala creates methods for you

Creating Classes

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.

Derived Setters and Getters

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   } 

Redefining Setters and Getters

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   } 

Why the Getter?

You might be wondering why we created the getter method def id(). Scala won’t allow us to use the shorthand assignment syntax to set a value unless the class has both the setter (id_=) and getter methods defined.

Summary

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.

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.

Назад: II. Key Syntactical Differences
Дальше: Classes and Objects