Inheritance review

Key concepts

  • Compile-time types

  • Run-time types and polymorphism

  • Constructor protocol

  • Private variables

Compile-time types are labels

When you declare a variable you are saying what kinds of values that variable can hold.

This also limits what you can do with that variable to only those things that you can do with that type.

Suppose we have these two classes

public class Animal {
  public void feed() { … }
}

public class Dog extends Animal {
  public void walk() { … }
}

We can feed any kind of animal.

But we can only walk dogs.

Animal critter = …;

Can we walk whatever’s in this crate?

Dog critter = …;

How about what’s in here?

The compiler can't keep track of much

Animal animal = new Dog("Fido");

The compiler will check that Dog is a subclass of Animal to make sure this is a safe assignment.

But it can't keep track of what animal really is. You've told it it’s an Animal so that's all it keeps track of.

To see the difficulties

Animal animal;
if (Math.random() < 0.5) {
  animal = new Dog("Fido");
} else {
  animal = new Rhinoceros();
}

The compiler can check that both Dog and Rhinoceros are subclasses of Animal so either assignment is safe.

But there’s no point trying to guess what animal is going to be beyond an Animal.

Given that, is this okay?

animal.walk()

No.

The compiler only knows that animal is an Animal and Animal doesn’t have a walk method.

Run-time method dispatching

The compiler just makes sure that every method call is legal. I.e. if we call animal.feed() there will be some feed method.

But once a program is running, the run-time type of the object, i.e. what class was actually used to construct the object, will determine which code is run.

Polymorphism

Any method call to an non-static method can be thought of as sending a message to that object saying, “Do the thing.”

This is the essence of polymorphism. How any given object will respond to that message is determined by the actual class of the object, not by what the compiler knows about its type.

For instance

public class Eater {
  public void eat(Food food) {
    System.out.println("Nom nom nom.");
  }
}

public class PickyEater extends Eater {
  public void eat(Food food) {
    System.out.println(isFavorite(food) ? "Yum" : "Yuck!");
  }
}

Polymorphic call

Eater eater = someKindOfEater();

Compiler knows eater is an Eater. So this is safe:

eater.eat(new Broccoli());

But at runtime it will be some particular kind of eater.

That runtime type will determine which eat method is called.

This is true even in code in the parent class

public class Dog {

  public void act() {
    System.out.println("run ");
    eat(); // this is still a polymorphic call
  }

  public void eat() {
    System.out.println("eat ");
  }
}

The call to eat in act may run the method defined in Dog or it may not.

super.method()

This is not a polymorphic call. It always invokes the method found in the direct parent class of the class containing the call.

public class A {
  public void foo() {...}
}
public class B extends A {
  public void bar() {
    super.foo(); // always invokes A's foo
    foo(); // invokes foo from actual class of object
  }
  public void foo() {...}
}
public class C extends B {
  public void foo() {...}
}

Constructors

“You have the right to a constructor. If you do not do not write a constructor, one will be provided for you.”

Some facts

  • All classes must have at least one constructor.

  • If you do not write a constructor, the compiler will add a no-argument constructor for you.

  • All constructors must, as the first thing they do, invoke a constructor from their parent class.

  • If a constructor does not explicitly invoke a parent constructor with super, the compiler will insert a call to super(), i.e. to a no-argument constructor.

These facts have some important implications

  • A constructor in a class whose parent class does not have a no-arg constructor must explicitly invoke one of the constructors the parent class does have.

  • When extending a class without a no-arg constructor you will have to write a constructor in order to invoke one of the parent's constructors.

The point

Remember that the job of a constructor is to make sure an object is properly initialized and ready to use.

These rules work to ensure that that stays true even in the face of inheritance.

Private variables

Not much to say here. private means private.

Subclasses can not directly reference private variables from their parent class.

Note that you can even have a variable in a subclass that is the same name as a private variable in the superclass. In such a case they are different variables.