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:
object TestCalculator extends App {
val calc = new MathCalculator
calc.add(12)
calc.mul(3)
calc.div(2)
calc.sin
println(calc.getResult) // prints -0.7509872467716762
calc.help // calls
MathCalculator.help which calls Calculator.help
}
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 over ridden.
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 hierarcy:
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.
MathCalculator extends Calculator, hence is a subtype of calculator. By the subsumption rule, this implies math calculators can masquerade as calculators:
var
calc2: Calculator = null
calc2 = new MathCalculator
Why
does the following command fail?
calc2.sin
To
remedy the situation we can perform a safe cast:
if
(calc2.isInstanceOf[MathCalculator]) {
val calc3 =
calc2.asInstanceOf[MathCalculator]
calc3.sin
println(calc3.getResult)
}
We
can use reflection to do the same thing:
if
(calc2.getClass == classOf[MathCalculator]) {
// as above
}
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)