The list structure is one of the most fundamental data structures in programming. The following is a common definition of a list.
We rephrase the above as follows.
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.
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 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.)
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.dxnguyen@cs.rice.edu, Alan Cox, last revised 01/23/02AShape t = new Rectangle(3, 4); // OK.
t = s; // OK, the old Rectangle is gone.
Circle u = new Rectangle(5, 6); // NO!