As in Java, classes can extend other classes. If class B extends class A, then B inherits the fields and methods of A. (Of course these fields and methods must be public or protected in order for B to access them.)
For example, assume a calculator class is declared in Scala:
class Calculator {
protected var result = 0.0
def add(x: Double) { result += x }
def mul(x: Double) { result *= x }
def div(x: Double) { result /= x }
def sub(x: Double) { result -= x }
def clear { result = 0.0 }
def getResult = result
def
help {
println("Commands: add, mul,
div, sub, clear, getResult, help")
}
}
We can create a math calculator by extending the Calculator class:
class MathCalculator extends Calculator {
def sin { result = math.sin(result) }
def cos { result = math.cos(result) }
def tan { result = math.tan(result) }
override def help {
super.help
println("Also:
sin, cos, tan")
}
}
The Calculator field and methods are inherited:
scala> val calc = new MathCalculator
calc: MathCalculator = MathCalculator@7bb58ca3
scala> calc.add(12)
scala> calc.mul(10)
scala> calc.div(4)
scala> calc.result
<console>:15: error: variable result in class Calculator cannot be
accessed in MathCalculator
Access to protected method result not
permitted because
enclosing object $iw is not a subclass
of
class Calculator where target is defined
calc.result
^
scala> calc.getResult
res4: Double = 30.0
scala> calc.sin
scala> calc.getResult
res6: Double = -0.9880316240928618
Notes:
· Class members can have public, private, and protected scope. Public members (the default) can be accessed by any client of the class. Private members can only be accessed by other members of the class. Protected members can be accessed by class members and members of subclasses. (How is this different from Java's definition of protected scope?)
· A class can override (i.e., redefine) inherited fields and methods, but must declare the override.
· An override may call the method overridden.
Continuing with the above example:
scala> var calc2: Calculator = calc
calc2: Calculator = MathCalculator@7bb58ca3
scala> calc2.add(.5)
scala> calc2.sin
<console>:16: error: value sin is not a member of Calculator
calc2.sin
^
scala> calc2.isInstanceOf[MathCalculator]
res11: Boolean = true
scala> calc2.asInstanceOf[MathCalculator].sin
scala> calc2.getResult
res12: Double = 0.005984152237500469
scala> calc2.help
Commands: add, mul, div, sub, clear, getResult, help
Also: sin, cos, tan
scala> calc2 = new Calculator
calc2: Calculator = Calculator@29e495ff
scala> calc2.add(.5)
scala> calc2.asInstanceOf[MathCalculator].sin
java.lang.ClassCastException: Calculator cannot be cast to
MathCalculator
... 32 elided
Notes:
· A class can be thought of as a type. (It might be more accurate to say that every class is associated with a type.)
· If we don't explicitly declare the type of calc2 to be Calculator, then Scala will infer from the initial value, calc2, that the type is MathCalculator.
· Polymorphism: if class A extends class B, then A is a subtype of B, and all instances of A can masquerade as instances of B. So math calculators like calc can be stored in calculator variables like calc2.
· However, MathCalculator-specific fields and methods can't be accessed for a masquerading math calculator. (Why?) That's why the calc.sin can't be called, even though calc.sin exists.
· We can "unmask" a masquerading math calculator by asking if it is, in fact, an instance of the MathCalculator class.
· We can temporarily retype the masqueraded math calculator by performing a cast.
· Casting an instance of Calculator to a MathCalculator results in disaster. (Why?) Check before you cast using isInstanceOf. This is called a safe cast.
· However, calling the help method of a math calculator masquerading as a calculator invokes the overridden method.
Here's how subclasses are represented in UML
Scala data can be divided into values and references. Values are objects that wrap primitive values such as Int, Boolean, and Double. All other objects are references. Here's the Scala inheritance hierarchy:
Notes:
· Any is the root of the Scala class hierarchy. Its methods include:
==, !=, equals
hashCode, ##
isInstanceOf, asInstanceOf
toString
· AnyVal is the root of all value types (i.e., wrappers for primitive values). Implicit subtype conversions are defined between these classes.
· AnyRef is the root class for all reference types (pointers to objects in the heap). In addition to the inherited methods, it adds:
getClass // returns an instance of java.lang.Class
· Unit is analogous to Java's void type. Its only instance is ().
· The only instance of Null is null, the null reference pointer. It can masquerade as any reference.
· Nothing is the empty class. It is the universal subtype.
Constructor parameter lists my also contain ordinary parameters that are neither var or val parameters. These don't turn into fields unless they are used in the body of the constructor. One application of such parameters is to initialize inherited fields:
class Date(val month: Int, val day:
Int, val year: Int) {
if (month <= 0 || 12 < month
|| day <= 0 || 31 < day || year < 0)
throw new Exception("Invalid
date")
override def toString = month + "/"
+ day + "/" + year
}
class Time(val hour: Int, val minute:
Int, month: Int, day: Int, year: Int)
extends Date(month, day, year) {
if (hour < 0 || 24 < hour ||
minute < 0 || 60 <= minute)
throw new Exception("Invalid
time")
override def toString = hour + ":"
+ minute + ":" + super.toString
}
Here are some sample calls:
var day1 = new Date(10, 2,
2018) //> day1 : time.Date = 10/2/2018
day1 //>
res0: time.Date = 10/2/2018
// day1.month = 5 error, month is a val
field
var time1 = new Time(10, 30, 10, 2, 1018) //> time1 : time.Time = 10:30:10/2/1018
time1 //>
res1: time.Time = 10:30:10/2/1018
try {
day1 = new Date(13, 1, 2019)
} catch {
case e: Exception => println(e)
} //>
java.lang.Exception: Invalid date
try {
time1 = new Time(10, 30, 13, 1, 2019)
} catch {
case e: Exception => println(e)
} //>
java.lang.Exception: Invalid date
}
An abstract class contains one or more abstract methods. An abstract method is simply a method without an implementation:
abstract class Shape {
def
area: Double
}
Abstract classes can't be instantiated for obvious reasons.
A class that inherits an abstract method is also abstract unless it provides implementations for those methods:
class Rectangle(length: Double, width: Double) extends Shape {
def area = length * width
}
Notes:
· We don't need to declare that a method overrides an inherited abstract method.
· We don't need to perform casts. Thanks to polymorphism, the right method always gets called:
var shape: Shape = new Rectangle(3, 4)