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"
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.)
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.
Recall the Pizza problem of comparing two pizzas for the better deal.
/**
* 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.edu, Alan Cox, last revised 01/22/03