Rice University - Comp 212 - Intermediate Programming

Spring 2003

Lecture #4 - OOP Fundamentals; Immutable List Structure 

I. 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.

In Java, the basic behaviors of all data are encapsulated and implemented in a class called Object.  For example, any instance of Object has a method called toString(), which returns the string consisting of the name of the class followed by the the at-sign character `@', and the unsigned hexadecimal representation of the hash code of the object!

    new Object().toString() may return the string "java.lang.Object@15273a"

When we program in Java, we rarely use the Object class directly.  Instead, we identify the various data and their behaviors in the problem domain and encapsulate them in appropriate user-defined classes.  Any user-defined class automatically has all the data (fields) and behaviors (methods) that are encapsulated in Object.  This concept is called "inheritance".  For example, the class Rectangle inherits all the methods and fields of Object.

    new Rectangle(4, 5).toString() may return the string "Rectangle@a43123"

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 (super class), 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.

The inheritance relationship between classes is transitive.  That is, if class A inherits from class B, and class B inherits from class C, then class A inherits from class C.  In Java, any user-defined class inherits from the Object class.  Whenever a class is defined without specifying any super class (also called parent class), it is automatically a (direct) subclass of Object.  For example, the classes Pizza, Circle, Rectangle, PizzaClient, PizzaDeal are all subclasses of Object.  The Object class is said to be at the root (or top) of the inheritance hierarchy.

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,

 

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

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

s.getArea(); // get the area of the circle of radius 2.7

t.getArea(); // get the area of a rectangle 3 x 4

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

t.getArea(); // What area is this?

s = new Rectangle(5, 6); // Is it OK?

// Has t changed at all at this point?

t.getArea(); // What area is this?

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

Object x = new Circle(4.3); // Is it OK?

x.getArea(); // Is it OK?

((Circle)x).getArea(); // This is called type-casting.  We will see more type-casting in future notes.

 

II. Immutable List Structure

Recall the Pizza problem of comparing two pizzas for the better deal.

PizzaDeal.java 
/**
 * Computes the price/area ratio of a Pizza to model the notion of a "deal".
 * Compares the deals between two Pizzas.
 */
public class PizzaDeal {

    /**
     * Computes the price/area ratio of a given Pizza.
     * @param p != null
     * @returns the price/area ratio of p.
     */
    public double deal(Pizza p) {
        return p.getPrice() / p.getShape().getArea();
    }

    /**
     * Compares the price/area ratios of the two Pizza parameters p1, and p2.
     * @param p1 != null
     * @param p2 != null
     * @returns true if p1 is a better deal than p2, false otherwise.
     */
    public boolean betterDeal(Pizza p1, Pizza p2) {
        return deal(p1) < deal(p2);
    }
}

What if we want to find the best deals among one or more pizzas?  Can we re-use what we have written without making any changes to the existing code?

To solve this problem, we will need a data structure to hold zero or more pizzas.  One of the most most fundamental data structures in programming is the list structure.  Remember Comp 210?  Instead of designing a list structure to hold only Pizza objects, we will formulate and implement a list structure to store any Object instances.

 
dxnguyen@cs.rice.eduAlan Cox, last revised 01/22/03