Rice University - Comp 212 - Intermediate Programming

Fall 2002

Lecture #4 - Immutable Recursive List Structure and OOP Fundamentals

I. Structure

The list structure is one of the most fundamental data structures in programming.   The following is a common definition of a list.

A list is either empty or non-empty.  If it is empty it contains no elements.   If is not empty, it contains an element called first and an element called rest, which is itself a list.

We rephrase the above as follows.

There is an abstract notion of a list.  A list knows how to return the element called first and the element called rest, which is itself another list.
An empty list is a list.  It has no first and no rest element.
A non-empty list is a list.  It has an element called first and an element called rest, which is itself another list.

We model this abstract notion of a list by an abstract class called AList, the empty list and the non-empty lists as a concrete subclasses of AList called EmptyList and NEList respectively.  The table below shows the differences between the object-oriented formulation in Java and the "data-directed" formulation in Scheme (a la Comp 210).

 

OO Design in Java Data-directed Design in Scheme
/**
*
A list is an abstract entity that intrinsically knows how to return
* its first data element and its rest, which is another list.

*/
public abstract class AList {
  public abstract Object getFirst();
  public abstract AList getRest();
}
;; A list is either
;;   (make-Empty) , the empty list
;; or
;; (make-NonEmpty f r) where
;; f is a data object and r is a list

/**
*
An empty list is a list. It has no first and no rest element.
*
It throws an exception when asked for first and rest.
*/
public class EmptyList extends AList {
  public Object getFirst() {
    throw new IllegalArgumentException ("EmptyList has no first!");
  }

  public AList getRest() {
    throw new IllegalArgumentException ("EmptyList has no rest!");
  }
}
(define-struct Empty())
;; make-Empty is automatically generated
/**
* A non-empty list is a list.  It has an element called first and an
* element called rest, which is itself another list.

*/
public class NEList extends AList {
  private Object _first;
  private AList _rest;

  public NEList(Object first, AList rest) {
    _first = first;
    _rest = rest;
  }

  public Object getFirst() {
    return _first;
  }

  public AList getRest() {
    return _rest;
  }
}
(define-struct NEList(first rest))
;; make-NEList,  NEList-first, NEList-rest

;; are automatically generated

 

The following UML diagram illustrates the above object-oriented design (click on the diagram to see the full documentation).  We have added the toString() method to return a String representation of an empty list and a non-empty list.

In the above diagram, the methods getFirst() and getRest() in AList are pure abstract, that is they contain no code body.  They are overriden in the concrete subclasses NEList and EmptyList with concrete code body to carry out the appropriate tasks.  The client should program only to the abstract class  AList.   Polymorphism will properly dispatch the method call to the appropriate concrete subclass at run-time.

 

II. Fundamentals of Object-Oriented Programming

Encapsulation

``Encapsulation is the process of compartmentalizing the elements of an abstraction that constitute its structure and behavior; encapsulation serves to separate the contractual interface of an abstraction and its implementation.'' [Booch]

In Java, we encapsulate the structural and behavioral elements of an abstraction in a class. These are the fields and the methods of the class, respectively. The attributes public and private are used to separate the interface from its implementation.

Inheritance

In OOP, inheritance is defined as the capability to derive new classes from existing classes. A derived class (subclass) inherits the fields and methods of the base class (superclass), and may add new fields and methods. A new method may have the same name as one in the base class, in which case it overrides the original one.

Inheritance is useful when a group of classes present a similar interface to the outside world, but have different internals.

Inheritance also facilitates a form of code re-use.  In effect, a subclass re-uses the superclass's code.  The subclass can extend the functionality of the superclass by adding new fields and methods.  The subclass can also replace functionality of the superclass by overriding fields and methods defined by the superclass.  (We will rarely, if ever, use inheritance in this way.  We will use class composition, the `has-a', rather than the `is-a' relation, in such cases.)

Polymorphism

Webster's dictionary offers the following definitions of polymorphism: ``2. (Biol.) (a) The capability of assuming different forms; the capability of widely varying in form. (b) Existence in many forms; the coexistence, in the same locality, of two or more distinct forms independent of sex, not connected by intermediate gradations, but produced from common parents.'' [Webster's Revised Unabridged Dictionary, (C) 1996, 1998 MICRA, Inc.]

In OOP, the definition of polymorphism more closely parallels that of Webster's dictionary than you might expect.  The term is applied to variables which may refer at run time to objects of different but related classes.  The classes are related in that they are subclasses of the same superclass.  Thus, they all inherit the interface of the superclass, obligating them to support any method (behavior) of the superclass.

Furthermore, in Java, a variable of a superclass type can be assigned any object created from a subclass, but not the other way around.  For example,

AShape s = new Circle(2.7); // OK.

AShape t = new Rectangle(3, 4); // OK.

t = s; // OK, the old Rectangle is gone.

Circle u = new Rectangle(5, 6); // NO!

 
dxnguyen@cs.rice.eduAlan Cox, last revised 01/23/02