Creating and Using Classes

Creating classes in Scala is shockingly expressive and highly concise. We’re going to explore how to create instances first, then how to create classes, and finally how to define fields and methods.

Creating Instances

Creating an instance of a class in Scala is not much different from creating instances in Java. For example, let’s create an instance of StringBuilder:

 
new​ ​StringBuilder​(​"hello"​)

That’s pretty much like in Java, except for the missing semicolon. We used new followed by the class name and the argument(s) for its constructor. The class StringBuilder has another overloaded constructor that doesn’t take any parameters. Let’s use that next:

 
new​ ​StringBuilder​()

That worked, but Scala programmers view the empty parentheses as noise. Incidentally, Scala does not require () when using new if the constructor takes no parameters. Let’s rewrite the code:

 
new​ ​StringBuilder

As you can see, creating instances is simple, but the real fun is in creating our own abstractions.

Creating Classes

Let’s start with a Java example for creating a class that follows the bean convention for properties:

WorkingWithObjects/Car.java
 
//Java example
 
public​ ​class​ Car {
 
private​ ​final​ ​int​ year;
 
private​ ​int​ miles;
 
 
public​ Car(​int​ yearOfMake) { year = yearOfMake; }
 
 
public​ ​int​ getYear() { ​return​ year; }
 
public​ ​int​ getMiles() { ​return​ miles; }
 
 
public​ ​void​ drive(​int​ distance) {
 
miles += ​Math​.abs(distance);
 
}
 
}

The class Car has two properties, year and miles, and the corresponding getter methods, called getYear and getMiles. The drive method manipulates the miles property, whereas the constructor initializes the final field year. In short, we have a couple of properties, as well as methods to initialize and manipulate them.

In Scala we don’t create a class; instead we write a constructor—much like in JavaScript. Think of the constructor as an object factory—it tells how an object should be constructed. Scala directs our focus on creating objects from the get-go.

Here’s Scala’s way to do the same thing as the Java code:

WorkingWithObjects/UseCar.scala
 
class​ Car(​val​ year: ​Int​) {
 
private​ ​var​ milesDriven: ​Int​ = 0
 
 
def​ miles = milesDriven
 
 
def​ drive(distance: ​Int​) {
 
milesDriven += Math.abs(distance)
 
}
 
}

In the Java version, we explicitly defined the field and method for the property year and wrote an explicit constructor. In Scala, the parameter to the class constructor—we refer to this informally as class—took care of defining that field and writing the accessor method. Here’s how we would use the Scala class:

WorkingWithObjects/UseCar.scala
 
val​ car = ​new​ Car(2015)
 
println(s​"Car made in year ${car.year}"​)
 
println(s​"Miles driven ${car.miles}"​)
 
println(​"Drive for 10 miles"​)
 
car.drive(10)
 
println(s​"Miles driven ${car.miles}"​)

And here’s the result of running the command scala UseCar.scala:

 
Car made in year 2015
 
Miles driven 0
 
Drive for 10 miles
 
Miles driven 10

Since Scala’s access control is public by default, the class Car is accessible from any package or file. Using the val keyword we defined year to be immutable, or final in Java. The field milesDriven is mutable—we used var instead of val to define it—however, it’s private since we explicitly decorated its access control. Let’s explore the details of defining members further.

Defining Fields, Methods, and Constructors

The conciseness that Scala offers continues when defining methods and constructors. As you learn to write Scala code, take a minute to look under the hood into the bytecode that Scala generates. This is a great way to learn what’s going on and nail some of the concepts firmly.

Scala rolls the class definition into the primary constructor and provides a concise way to define fields and corresponding methods. Let’s look at a few examples.

Let’s start with the following concise class definition:

WorkingWithObjects/CreditCard.scala
 
class​ CreditCard(​val​ number: ​Int​, ​var​ creditLimit: ​Int​)

There’s no need for curly braces ({}) if the class definition has no body.

That’s it. That’s a full definition of a class with two fields, one constructor, a getter for the immutable number field, and both a getter and a setter for the mutable creditLimit field. That one line of Scala code is equivalent to well over ten lines of Java code. Oh dear, you may wonder, if the code’s so concise, what would programmers do with the time saved from not having to type all that extra code? Go home—no need to waste time writing silly code that the compiler can generate.

To examine how the Scala compiler transformed the concise code into a full-blown class, compile the previous code using the command scalac CreditCard.scala, and run the command javap -private CreditCard to see what the compiler generated:

 
Compiled from "CreditCard.scala"
 
public class CreditCard {
 
private final int number;
 
private int creditLimit;
 
public int number();
 
public int creditLimit();
 
public void creditLimit_$eq(int);
 
public CreditCard(int, int);
 
}

Let’s examine the code synthesized by the compiler. First, Scala automatically made the class public—the default access control in Scala.

We declared number as val, so Scala defined number as a private final field and created a public method number to help fetch that value. Since we declared creditLimit as var, Scala defined a private field named creditLimit and gave us a public getter and setter for it. The default generated getters and setters do not follow the JavaBean conventions; we’ll soon see how to control that.

We declared one of the constructor parameters as val and the other as var. If you declare a constructor parameter without either of those keywords, then the compiler will create a private final field for internal access within the class. Fields generated from such parameters don’t get any accessors and are not accessible from outside the class.

Scala will execute as part of the primary constructor any expression or executable statement directly placed into the class definition. Let’s take a look at an example to see this:

WorkingWithObjects/Construct.scala
 
class​ Construct(param: ​String​) {
 
println(s​"Creating an instance of Construct with parameter $param"​)
 
}
 
 
println(​"Let's create an instance"​)
 
new​ Construct(​"sample"​)

The call to println directly appears in the class definition in the previous code. We can see in the output that this code is executed as part of the constructor call:

 
Let's create an instance
 
Creating an instance of Construct with parameter sample

In addition to the fields declared using the primary constructor’s parameters, you can define other fields, methods, and zero or more auxiliary constructors. In the following code, the this method is an auxiliary constructor. We are also defining the variable position and overriding the toString method:

WorkingWithObjects/Person.scala
 
class​ Person(​val​ firstName: ​String​, ​val​ lastName: ​String​) {
 
var​ position: ​String​ = _
 
 
println(s​"Creating ${toString}"​)
 
 
def​ ​this​ (firstName: ​String​, lastName: ​String​, positionHeld: ​String​) {
 
this​ (firstName, lastName)
 
position = positionHeld
 
}
 
override​ ​def​ toString : ​String​ = {
 
s​"$firstName $lastName holds $position position"
 
}
 
}
 
 
val​ john = ​new​ Person(​"John"​, ​"Smith"​, ​"Analyst"​)
 
println(john)
 
val​ bill = ​new​ Person(​"Bill"​, ​"Walker"​)
 
println(bill)

The primary constructor that’s combined with the class definition takes two parameters, firstName and lastName. You can easily make the primary constructor private if you desire; see Stand-alone and Companion Objects.

In addition to the primary constructor, we have an auxiliary constructor, defined using the method named this. It takes three parameters: the first two are the same as in the primary constructor, and the third is positionHeld. From within the auxiliary constructor, we’re calling the primary constructor to initialize the name-related fields. Scala strictly enforces a rule that the first statement within an auxiliary constructor is required to be a call to either the primary constructor or another auxiliary constructor.

Here’s the output from the previous code:

 
Creating John Smith holds null position
 
John Smith holds Analyst position
 
Creating Bill Walker holds null position
 
Bill Walker holds null position

In Scala fields are given special treatment. Any var defined within a class is mapped to a private field declaration followed by the definition of corresponding getter and setter accessor methods. The access privilege you mark on the field is actually used for accessor methods. That’s the reason, in the previous example, that even though we marked the position field as public, the default accessor in Scala, the compiler created a private field and public accessors. You can see this in the following excerpt of running javap on the compiled bytecode:

 
private java.lang.String position;
 
public java.lang.String position();
 
public void position_$eq(java.lang.String);

The declaration of position turned into a field definition plus a special getter method named position and a setter method named position_=.

In the previous definition of position, we could have set the initial value to null. Instead, we used an underscore (_). Scala provides the convenience of initializing var to its default value using the underscore—this can save some keystrokes since Scala requires variables to be initialized before use. In the context of the previous example, the _ stands for the default value for the type—so, for Int, it will evaluate to 0 and for Double, it would be 0.0. For a reference type, it evaluates to null, and so on. This convenience of initialization using an underscore is not available for variables declared using val, however, since they can’t be modified after creation. Therefore, you’re required to give appropriate values when initializing.

We explored how concise defining methods and constructors are in Scala and also looked under the hood to get a grasp of the actual code that Scala generated. Although concise code gets converted to full-blown classes during compilation, the names of the generated accessor methods don’t follow the JavaBean convention—that may raise some eyebrows. Thankfully, it’s easy to fix.

..................Content has been hidden....................

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