08. Inheritance - RobertMakyla/scalaWiki GitHub Wiki

KEY POINTS:

  • The 'extends' and 'final' keywords are as in Java.
  • You MUST use 'override' when you override a method that isn't abstract.
  • Only the primary constructor can call the primary superclass constructor.
  • You CAN override fields.
  • Extending a class - as in java, a class/field/method can be 'final' so that you cannot extend/override it.
  • final field is like val in scala (attempt of reassigning causes error)

###Overriding

class Person (val name:String) {
    override def toString = getClass.getName + "[name=" + name + "]"
}

I must use override if supermethod is not abstract The override causes error when:

  • I misspell the name
  • I provide wrong parameters type
  • I add new method in superclass that clashes with subclass method

In order to call super class method I use 'super.' prefix :

class Person (val name:String) {
    override def toString = super.toString + " : " + getClass.getName + "[name=" + name + "]"
}

class Me(override val name:String) extends Person(name){}

###Type Checks and Casts

(in Scala there is a method: isInstanceOf[] / asInstanceOf[] )

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

null.isInstanceOf[Person]   // --> false
null.asInstanceOf[Person]   // --> null

"Hi".isInstanceOf[Person]   // --> false
"Hi".asInstanceOf[Person]   // --> throws ClassCastException !!!

 (new Me("Rob")).isInstanceOf[  Me  ]   // -->true
 (new Me("Rob")).isInstanceOf[Person]   // -->true

To test whether value refers is of supertype, but not a subtype, use

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

the classOf[] method is defined in 'scala.Predef'

-- SCALA --                  -- JAVA --
obj.isInstanceOf[T]          obj instanceOf T
obj.asInstanceOf[T]          (T) obj
classOf[T]                   T.class

###Pattern matching - best way of recognizing type

p match {
    case emp: Employee => ... // Process emp as an Employee
    case _ => ...            // p wasn't an Employee
}

val me = new Me("rob")
me match {
    case m:Me     => println("it's me")
    case p:Person => println("it's a person")  // unreachable code
}

me match {
    case p:Person => println("it's a person")
    case m:Me     => println("it's me")        // unreachable code
}

me.asInstanceOf[Person] match {
    case p:Person => println("it's a person")
    case m:Me     => println("it's me")        // unreachable code
}

me.asInstanceOf[Person] match {
    case m:Me     => println("it's me")        // ok   -->  it's me
    case p:Person => println("it's a person")
}

###Protected fields and methods

protected - in scala it is visible only from subclasses (in java also in the same package, regardless class hierarchy)

protected[this] - restricts access to current object

###Superclass construction

class Employee(val name: String, val age: Int, val salary : Double) extends Person(name)
// ERROR, val name exists in Person - must override name

class Employee(override name: String, val age: Int, val salary : Double) extends Person(name)
// ok cause name is overridden

class Employee(name: String, val age: Int, val salary : Double) extends Person(name)
// ok cause name is object-private field in Employee

The 'name' is a param that is passed to superclass constructor. Scala class can extend java class, calling it's constructor like this:

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

###Abstract

The 'abstract' keyword is only for Classes, Traits..

The val/var/def becomes abstract simply when they have no values/body declared (then class/trait which contains it, must be abstract)

###Overriding Fields

abstract class Person {
    def id: Int
}

class Student(val id: Int) extends Person   // don't need 'override' - id is abstract

or:

class Person(val id:Int)

class Student(override val id: Int) extends Person(id)   // MUST use 'override' cause Person.id is concrete

Note: def can only override another def Note: val can only override another val or parameterless def (with parentheses or not)

class Parent {
    def hi ="parent says hello"
}

class Child extends Parent{
    override val hi = "child says Hello"    // ok
}

or

class Parent {
    def hi {println("parent says hello")}
}

class Child extends Parent{
    override val hi:Unit = {}              // ok
}

Note: var can only override an abstract var or a pair getter/setter (overriding just getter is an error) Note: var cannot override def because 'override var sth' tries to override getter (ok) and setter (not existing)

abstract class Parent {
    var age:Int
}

class Child extends Parent{
    override var age = 0      // ok
}

###Anonymous Subclasses

class Person(val name:String)

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

it creates object of type: Person{def greeting: String}

def meet(p: Person{def greeting: String}) {    // arg: Person but with greeting method
    println(p.name + " says: " + p.greeting)   // here I can use greeting on object Person
}

meet(alien)

###Abstract Classes

Cannot be instantiated. Its subclass may be instantiated if it implements all abstract methods.

abstract class Person(val name: String) { // concrete val
    def id: Int                          // abstract method
    def age: Int = 30                   // concrete method
}

In scala I don't need 'abstract' before method name. All I need to do it skip the body declaration

In scala as in Java, if there's at least 1 abstract method, I must add 'abstract' before class

class Employee(name: String) extends Person(name) {  // Employee's name is object-private
    def id = name.hashCode // 'override' not required as it was abstract in superclass
}

###Abstract Fields - field without initial value

Just as abstract method, abstract field must be in abstract class

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
}

class Employee(val id: Int) extends Person { // no need to 'override' cause super field was abstract
    var name = ""                           // no need to 'override' cause super field was abstract
}

I can also initialize an abstract field, by using anonymous type:

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

###Construction Order and Early Definitions

class Creature {
    val size: Int = 10
    val myArray: Array[Int] = new Array[Int](size)  // calls getter of size from Ant
}                                                  // gets 0 cause at this stage that is Ant's size

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

Note: Even though if it seems that size is either 10 or 2, the size of Array will be 0 !!!

Moral: Don't rely on the value of 'val' in the body of constructor because val can be overridden in subclass, and the getter from subclass may returns something that you don't expect

Remedies:

  1. :-/ Declare the val as final. This is safe but not very flexible.
  2. :-/ Declare the val as lazy in the superclass. This is safe but a bit inefficient.
  3. :-) Use the early definition syntax (BEST APPROACH) :

###Early Definitions - initializes all val fields before superclass is executed !!!

class Ant extends {
    override val size = 2
} with Creature {}

###Hierarchy (Any, AnyVal, AnyRef)

class AnyVal: all primitive-alike objects (Int, Long, Short, Boolean, etc.., Unit )
class AnyRef: all other classes (scala or java)

class AnyRef and AnyVal extend Any (root of hierarchy)
class Any has methods: isInstanceOf[T], asInstanceOf[T] )

class AnyVal - does not add any methods
class AnyRef - adds wait/notify/notifyAll and synchronized method (equivalent to synchronized block in java)

trait ScalaObject - has no methods - all scala objects implement it

trait Null   - trait of AnyRef
             - the only instance of Null type is null.

trait Unit   - trait of AnyVal
             - e.g. {println("hi")}    is of type Unit

trait Nothing - trait of Any
              - e.g.   throw new Exception("")   is of type Nothing

Note: it is impossible to assign Null to Int (it's better than in java where I could have Integer i = null) that's because Null (AnyRef) and Int (AnyVal) are not in one hierarchy ladder

In scala, java's 'void' is represented by Unit which extends AnyVal

def printUnit(x: Unit) { println(x) }
printUnit("Hello")  // Replaces "Hello" with () and calls printUnit(()), which prints ()

###Equality

 AnyRef.eq method checks if REFERENCES are equal

 AnyRef.equals calls AnyRef.eq // so again REFERENCES

###The == operator

The expression a == b is equivalent to

if (a eq null) b eq null else a.equals(b)

null == null       // true
null eq null       // true
null.equals(null)  // NullPointerException

Normally I should NEVER use eq or equals.

Simply use the == operator. For reference types, it calls equals after doing the appropriate check for null operands.

equals should be overridden in scala as in Java, to check properties and not references :

class Item (val description: String) {
    override def equals(other: Any) = {         // other must be of type Any, just like in java
        if ( other.isInstanceOf[Item])         // if other is null or not Item --> false (no NullPointerException)
             description == other.asInstanceOf[Item].description
        else
            false
    }
}

new Item("aa") == new Item("aa")          // --> true
new Item("aa").equals( new Item("aa") )   // --> true

When I define equals, I should also define hashCode