Methods and Inheritance

Inheriance is a mechanism in which we can cause one class to gain the public
and protected portion of another. The keyword extends is used to do
this. More specifically, by using the keyword extends we can tell the compiler
that one class is a subset, or rather subclass, of another (the superclass). In
the book this is called an "is a" relationship. For example the inheritance
hierarchy
             Person
               |
               |
        ---------------
        |             |
        |             |
        |             |
    Student       Teacher
Here we see that a Student and a Teacher are particular kinds of People. The classes
Student and Teacher share what they inherit from the class Person in common.
The code for setting up this relation would be, for example
public class Person
  {
  //instance variables accessed through
  //set and get methods. Hence they can be private
  private String firstname;
  private String lastname;
  private Date dateOfBirth; //We would have to define the class Date
  public Person(String a, String b, Date d)
    {
    //constructor calls set methods
    }
  //set methods for firstname, lastname  and dateOfBirth
  //get methods for firstname, lastname  and dateOfBirth
  //toString method
  }

public Student extends Person
  {
  private double gpa;
  public Student(String a, String b, Date d, double g)
    {
    super(a,b,d);//call constructor of superclass
    setGpa(gpa);
    }
  //setGpa and getGpa methods
  public String toString()
    {
    return super.toString()+" "+gpa;
    }
  }
Things to know (Read chapter 9 as well)
  1. Constructors: The constructor of the superclass will be called by the constructor
    of the subclass explicitly or implicitly. Explicitly I use the keyword super to refer
    to the constructor of the super class. Nonetheless, even if I dont call the super constructor
    explicitly (by typing super) it will be called anyway implicitly with no arguments.
  2. finalizers: Similarly I may call the superclass finalizer as super.finalizer or allow it
    to be called implicitly.
  3. Superclass Methods: Notice the the method toString appears in Person and Student. The toString
    in Student overrides the toString in Person. For a subclass method to override a superclass
    method they must have the same signiture (name and number and type of paramenters). In order to call
    the overriden method within the subclass one must affix super to it.
  4. References: A superclass reference may refer to a subclass object. However one can only call methods
    whose signature appear in the superclass definition using a superclass reference no matter what
    the reference refers to.
  5. Dynamic Binding: When using a superclass reference to refer to a subclass object, if an overriden
    method is called then the method in the subclass is used. For example
    Person p = new Student( initialization values );
    p.toString();//calls toString method defined in Student
  6. final Methods: Defining a method as being final prevents it from being overriden. For example if we had
    public final String toString()
      {
      }
    in Person, we would not have been able to define another toString in Student.
  7. final Classes: Defining a class as being final makes it impossible to inherit from the class
  8. A Note: If the superclass doesn't contain the signiture for a particular method defined
    in the subclass then we cannot use a superclass reference to call this method. We must explicit
    do a cast conversion to get a subclass refernce. For example
    Person p = new Student( initialization values );
    double theGpa = ( (Student) p ).getGpa(); //the cast (Student) returns a Student reference
                                              //while the reference p is left unchanged
  9. abstract Classes: abstract classes are classes that lack the definition of one of their
    methods. abstract classes cannot be instantiated but may be references. Lets see an example
    public abstract class Parcel
      {
      String nameOfParcel;
      protected static int numberOfParcels;
    
      public Parcel(String name)
        {
        setnameOfParcel(name);
        }
      setnameOfParcel(String name)
        {
        nameOfParcel=name;
        }
      public String getNameOfParcel()
        {
        return nameOfParcel;
        }
      public String toString()
        {
        return nameOfParcel;
        }
      public abstract double priceOfParcel( );
      }
    
    public class Letter extends Parcel
      {
      private int numberOfPages;
    
      public Letter(int n)
        {
        setNumberOfPages(n);
        }
      //set and get methods
      public double priceOfParcel( )
        {
        return .35;
        }
      //toString method
      }
    
    public class Box extends Parcel
      {
      private int numberOfPounds;
      private double pricePerPound;
    
      public Box(int n,double p)
        {
        setNumberOfPounds(n);
        setpricePerPound(p);
        }
      //set and get methods
      public double priceOfParcel( )
        {
        return pricePerPound*numberOfPounds;
        }
      //toString method
      }
    In this example Parcel is an abstract class with subclasses Letter and Box. Letters
    always cost thirtyfive cents and Boxes cost according to their weight. We could have simply
    left the method priceOfParcel out of the definition of Parcel altogether since each type
    of Parcel has its own way of determining price. Nonetheless we new each subclass of
    Parcel would need a priceOfParcel method, so we simply made it an abstract method.