© Toby Weston 2018

Toby Weston, Scala for Java Developers, https://doi.org/10.1007/978-1-4842-3108-1_9

9. Classes and Objects

Toby Weston

(1)London, UK

In this chapter we’ll look at the following:

  • How you can define fields within the class body rather than on the class definition line and how this affects the generated methods.

  • How you create additional constructors.

  • Scala’s singleton objects defined with the object keyword.

  • Companion objects, a special type of singleton object.

Classes Without Constructor Arguments

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 initialize 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() {
      count += 1
    }


    def getCount = count
  }

Within the primary constructor (that is, not in the class definition but immediately afterwards in the class body), the val and var keywords may generate getters and setters . Their presence affects the bytecode as shown in Table 9-1.

Table 9-1 How the presence of getters and setters affects the bytecode

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 Table 8-1. 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).

Additional Constructors

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

Using Default Values

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 in the Java version 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 would need to name default values when we don’t supply all the arguments in the order they’re declared. 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")

Singleton Objects

In Java you can enforce a single instance of a class using the singleton pattern. Scala has adopted this idea as a feature of the language itself: 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().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 the generated constructor is private.

A method on a Scala object is equivalent to static methods in Java. An example is the static main method on a Java class that can be executed by the runtime. You create a main method in a Scala singleton object, not a class to replicate the behavior.

Companion Objects

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 lastId;

      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 lastId++;
      }


  }

It’s static because we want to share its implementation among all instances to create globally 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 lastId = 0


    private def nextId(): Integer = {
      lastId += 1
      lastId
    }
  }

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 that follows 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 lastId = 0


    private def nextId(): Integer = {
      lastId += 1
      lastId
    }
  }

Other Uses for Companion Objects

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)
    def nextId() = 1
  }

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 lastId;

      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 = addres s;
          this.id = Customer.nextId();
      }


      private static Integer nextId() {
          return lastId++;
      }
  }
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.143.5.201