Scala for the Impatients---(8)Inheritance

In this chapter, we only discuss the case in which a class inherits from another class. See Chapter 10 for inheriting traits —the Scala concept that generalizes Java interfaces.

Extending a Class

You extend a class in Scala just like you would in Java—with the extends keyword:

class Employee extends Person {
var salary: 0.0
...
}

As in Java, you specify fields and methods that are new to the subclass or that override methods in the superclass.

As in Java, you can declare a class final so that it cannot be extended. You can also declare individual methods or fields final so that they cannot be overridden. Note that this is different from Java, where a final field is immutable, similar to val in Scala.

Overriding Methods

 

When overriding a non-abstract method, we should use the override modifier.

class Person {
...
override def toString = getClass.getName + "[name=" + name + "]"
}

Invoking a superclass method in Scala works exactly like in Java, with the keyword super :

class Employee extends Person {
...
override def toString = super.toString + "[salary=" + salary + "]"
}

Type Checks and Casts

To test whether an object belongs to a given class, use the isInstanceOf method. If the test succeeds, you can use the asInstanceOf method to convert a reference to a subclass reference:

if (p.isInstanceOf[Employee]) {
val s = p.asInstanceOf[Employee] // s has type Employee
...
}

The p.isInstanceOf[Employee] test succeeds if p refers to an object of class Employee or its subclass (such as Manager ).

If you want to test whether p refers to an Employee object, but not a subclass, use

if (p.getClass == classOf[Employee])

The classOf method is defined in the scala.Predef object that is always imported.

Protected Fields and Methods

Superclass Construction

The auxiliary constructors of the subclass eventually call the primary constructor of the subclass. Only the primary constructor can call a superclass constructor. Recall that the primary constructor is intertwined with the class definition. The call to the superclass constructor is similarly intertwined. Here is an example:

class Employee(name: String, age: Int, val salary : Double) extends Person(name, age)

In Java, the equivalent code is quite a bit more verbose:

public class Employee extends Person { // Java
private double salary;
public Employee(String name, int age, double salary) {
super(name, age);
this.salary = salary;
}
}

In a Scala constructor, you can never call super(params) , as you would in Java, to call the superclass constructor.

A Scala class can extend a Java class. Its primary constructor must invoke one of the constructors of the Java superclass. For example:

class Square(x: Int, y: Int, width: Int) extends
java.awt.Rectangle(x, y, width, width)

  Overriding Fields

You can override a val (or a parameterless def ) with another val field of the same name. The subclass has a private field and a public getter, and the getter overrides the superclass getter (or method).

class Person(val name: String) {
    override def toString = getClass.getName + "[name=" + name + "]"
}
class SecretAgent(codename: String) extends Person(codename) {
    override val name = "secret" // Don't want to reveal name . . .
    override val toString = "secret" // . . . or class name
}

A more common case is to override an abstract def with a val , like

abstract class Person { // See Section 8.8 for abstract classes
def id: Int // Each person has an ID that is computed in some way
...
}
class Student( override val id: Int) extends Person
// A student ID is simply provided in the constructor

Anonymous Subclasses

you make an instance of an anonymous subclass if you include a block with definitions or overrides, such as

val alien = new Person("Fred") {
def greeting = "Greetings, Earthling! My name is Fred."
}

Technically, this creates an object of a structural type —see Chapter 18 for details. The type is denoted as

Person(def greeting: String)

You can use this type as a parameter type(就是说这个type可以做函数参数):

def meet(p: Person{def greeting: String}) {
    println(p.name + " says: " + p.greeting)
}

Abstract Classes

you can use the abstract keyword to denote a class that cannot be instantiated, usually because one or more of its methods are not defined. For example

abstract class Person(val name: String) {
def id: Int // No method body—this is an abstract method
}

In Scala, unlike Java, you do not use the abstract keyword for an abstract method. A class with at least one abstract method must be declared abstract.

In a subclass, you need not use the override keyword when you define a method that was abstract in the superclass.

class Employee(name: String) extends Person(name) {
def id = name.hashCode // override keyword not required
}

Abstract Fields

An abstract field is simply a field without an initial value. For example 

abstract class Person {
val id: Int
// No initializer—this is an abstract field with an abstract getter method
var name: String
// Another abstract field, with abstract getter and setter methods
}

Concrete subclasses must provide concrete fields, for example:

class Employee(val id: Int) extends Person { // Subclass has concrete id property
var name = "" // and concrete name property
}

As with methods, no override keyword is required in the subclass when you define a field that was abstract in the superclass.

You can always customize an abstract field by using an anonymous type:

val fred = new Person {
val id = 1729
var name = "Fred"
}

Construction Order and Early Definitions

When you override a val in a subclass and use the value in a superclass constructor, the resulting behavior is unintuitive.

class Creature {
    val range: Int = 10
    val env: Array[Int] = new Array[Int](range)//getter method-range
}

class Ant extends Creature {
    override val range = 2
}

So what is the problem? Let's consider the process:

1. The Ant constructor calls the Creature constructor before doing its own construction.
2. The Creature constructor sets its range field to 10 .
3. The Creature constructor, in order to initialize the env array, calls the range() getter.
4. That method is overridden to yield the (as yet uninitialized) range field of the Ant class.
5. The range method returns 0. (That is the initial value of all integer fields when an object is allocated.)
6. env is set to an array of length 0 .
7. The Ant constructor continues, setting its range field to 2 .

Even though it appears as if range is either 10 or 2 , env has been set to an array of length 0. The moral is that you should not rely on the value of a val in the body of a constructor.

There are several remedies.
  • Declare the val as final . This is safe but not very flexible.
  • Declare the val as lazy in the superclass (see Chapter 2). This is safe but a bit inefficient.
  • Use the early definition syntax in the subclass—see below.

The “early definition” syntax lets you initialize val fields of a subclass before the superclass is executed. You place the val fields in a block after the extends keyword, like this:

class Bug extends {
    override val range = 2
} with Creature

Note the with keyword before the superclass name. This keyword is normally used with traits—see Chapter 10.

The Scala Inheritance Hierarchy

 

posted on 2016-09-16 17:05  chaseblack  阅读(234)  评论(0编辑  收藏  举报

导航