Lecture3

Inheritance

Inheritance in Java

Polymorphism

Child classes can also override the parent's methods. This is useful if,
       a) the child's behavior is slightly different form the parents or,
       b) certain behavior is not defined for the parent, but is for all
          child classes. In this case the method is usually defined in
	  the parent class, and overridden on all child classes.

Another example: Say we have a generic class for representing nodes
in a binary tree. Each node in the tree can have a left and right
descendent. Leaf nodes are merely tree nodes with no children.


abstract class ExprNode{
    private ExprNode left = null;
    private ExprNode right = null;
    public ExprNode();        // constructs leaf node (no children)
    public ExprNode(ExprNode l,ExprNode r);    // construct interior node
    public ExprNode getLeft();
    public ExprNode getRight();
    public void setLeft(ExprNode n);
    public void setRight(ExprNode n);
    public boolean isLeaf();   // returns true if no children
    // add later
    public abstract int evaluate(); // compute value

}

Now, say we want ot use this to represent and evaluate arithmetic
expression tree such as ((3 + 4) * (2 - 1)). 
We can represent this
as a tree where the interior nodes hold the operator and the
leaf node the literals. We need to extend ExprNode with 2 new types

/**
 * Node type for literal values
 */
class LitNode extends ExprNode{
   // instance var
   private int value;  // value for this node
   // Constructor
   LitNode(int value);     // always a leaf node
   public int getValue();  // accessor
}

class OpNode extends ExprNode{
   // Lame Java syntax for defining class constants
   // (note 'final' keyword, prevents assignment)
   static public final int PLUS = 1;
   static public final int TIMES = 2;
   static public final int MINUS = 3;
   // instance var
   private int operator;  // operator for this node
   // Constructor
   OpNode(int op,ExprNode l,ExprNode r);     // op must match one of above

}


Now we can build a tree

   // Note: we can use cariable of parent type to hold child. Java does
   //  explicit upcasting
   ExprNode t1 = new LitNode(2);
   ExprNode t2 = new LitNode(1);
   ExprNode tree = new OpNode(OpNode.TIMES,t1,t2); // build ( 2 * 1 )

However, we still need to add evaluate() routine to OpNode and LitNode.

class LitNode extends ExprNode{
   ... stuff from before...
   public int evaluate(){return value;}
}

class OpNode extends ExprNode{
 ...stuff ...
 public int evaluate(){
     // Unfortunately we still have to downcase, but the branch on
     // node type is no longer necessary
    ExprNode el = getLeft();
    int lval = el.evaluate();
    int rval = getRight().evaluate();   // shorter form
    if(op == PLUS) return(lval + rval)
    else if(op == TIMES) return(lval * rval)
    else    ....

 }
}

Even though we are referring to an instance with a variable typed
as the parent type (here ExprNode), Java remembers the complete type
of the instance. When we call a method on the instance, Java uses the
appropriate method for each instance subtype (as it we had cast to
it's actual type). This is called "polymorphism" and is much of
the power of inheritance and OOP.

We could further use polymorphism to remove the branching on operator
type. How?

Notes on inheritance


Recitation