Suppose that you want to develop a class to model the behavior of a counter, i.e., an object with an integer value that you can increment, decrement, or set to a desired value. You might produce something like the following:
public class Counter {
/* i n s t a n c e v a r i a b l e s */
private int cntrVal; // current value of the counter
/* c o n s t r u c t o r s */
/**
* pre: true
* post: this.countVal() == init
*/
public Counter(int init)) { cntrVal = init; }
/**
* pre: true
* post: this.countVal() == 0
*/
public Counter() { this(0); }
/* o b s e r v e r s */
public int countVal() { return cntrVal; }
/* m u t a t o r s */
/**
* pre: let c == this.countVal()
* post: this.countVal() == c + 1
*/
public void increment() { cntrVal = cntrVal + 1; }
/**
* pre: let c == this.countVal()
* post: this.countVal() == c - 1
*/
public void decrement() { cntrVal = cntrVal - 1; }
/**
* pre: true
* post: this.countVal() == newVal
*/
public void setTo(int newVal) { cntrVal = newVal; }
}
Suppose that, after the class Counter has "gone into production" (and hence, by your company's rules, may not be modified), a need arises for a "reset-able" counter, i.e., one having a reset operation by which it can be made to return to its original value. We could develop a new class for this, making use of Counter as follows:
public class ResetableCounterViaComposition {
/* i n s t a n c e v a r i a b l e s */
private Counter c;
private int initVal;
/* c o n s t r u c t o r s */
public ResetableCounterViaComposition(int initCntrVal)) {
initVal = initCntrVal;
c = new Counter(initCntrVal);
}
public ResetableCounterViaComposition() { this(0); }
/* o b s e r v e r s */
public int countVal() { return c.countVal(); }
/* m u t a t o r s */
public void increment() { c.increment(); }
public void decrement() { c.decrement(); }
public void setTo(int newVal) { c.setTo(newVal); }
public void reset() { c.setTo(initVal); }
}
Notice that each instance of ResetableCounterViaComposition contains, as one of its fields, a reference to an instance of Counter. When an instance of a class A has as fields (references to) instances of classes A1, A2, ..., Ak, we say that A is built via composition from A1, A2, ..., Ak. The use of composition indicates that there is a has-a relationship between, in this case, instances of A and instances of each of the Ai's.
To illustrate what's going on, suppose that an instance of ResetableCounterViaComposition currently has value 7 (i.e., is in a state such that an invocation of countVal() yields 7) and had 0 as its initial value. Then the situation is as depicted below, in which there is an instance of ResetableCounterViaComposition (on the left) whose initVal field contains zero and whose c field contains a reference to an instance of Counter (on the right) whose cntrVal field has value 7:
+-----------------+
| +---+ |
| initVal | 0 | | +------------------+
| +---+ | | +---+ |
| c | *-|---+------------->| cntrVal | 7 | |
| +---+ | | +---+ |
+-----------------+ +------------------+
instance of instance of
ResetableCounterViaComposition Counter
With the exception of reset(), every method in ResetableCounterViaComposition is implemented by invoking the corresponding method on its instance variable c. Thus, even though each such method is "trivial" to implement, the fact remains that we have to supply the code for it.
If, instead of viewing a reset-able counter as an object that contains a (plain) counter object as one of its components, we view it as a special kind of counter object, we obtain a more elegant result. The idea of an object of a class B being a special kind of object of another class A is supported in Java (and other object-oriented languages) by the mechanism of inheritance, or subclassing, or extension. Here we would define B to inherit from (or be a subclass of, or extend) A. Using this mechanism, we extend Counter to arrive at a subclass modeling a reset-able counter:
public class ResetableCounter extends Counter {
/* i n s t a n c e v a r i a b l e s */
private int initVal; // initial value
/* c o n s t r u c t o r s */
public ResetableCounter(int initCntrVal) {
super(initCntrVal); // calls constructor of the superclass
initVal = initCntrVal;
}
public ResetableCounter() { this(0); } // call to other constructor
/* n e w m u t a t o r s */
public reset() { setTo(initVal); }
}
When a class B is built by extending a class A, we say that B is a subclass of A or, equivalently, that A is a superclass of B. (We also say that B is derived from A via extension.) More concretely, what this means is that each instance of B has all the fields (and methods) that an instance of A has, plus whatever fields (and methods) are declared in B. We say that instances of B inherit the attributes/features (i.e., fields and methods) of class A. In particular, no mention needs to be made inside B of the attributes it inherits from A.
As an aside, we note that there are rules governing the accessibility of inherited attributes. For example, an instance variable (or method) declared to be private in class A cannot be referred to inside its child class B.
In extending a class A to derive a subclass B, typically what we do is to introduce instance variables and methods that do not exist in A. In our example, ResetableCounter introduces an instance variable initVal and a method reset(). (Also, as is typically the case, one or more constructors are included in a subclass, because among the actions taken to initialize a new instance of the subclass is to initialize the values of the newly-introduced instance variables.)
Although there was no need for it in ResetableCounter, a subclass may override one or more methods of its parent. If B is a subclass of A, and B contains a method f() having the same name and signature (number and types of parameters) as some method in A, we say that the method f() in B overrides the like-named method in A. What this means is that, if b is (a reference to) an instance of B, the invocation b.f() will be of B's method, not A's.
Let us explore the issues of inheritance further by supposing that a need arises for a counter having specified minimum and maximum values and the property that its value "rolls over" (like an old-fashioned mechanical automobile odometer) when it is incremented beyond its maximum value or decremented beyond its minimum value.
We could use either composition or inheritance to build this class. Using inheritance makes more sense, however, because the kind of counter we are describing is just a special case of the kinds of counters we considered above.
public class RollOverCounter extends ResetableCounter {
/* n e w i n s t a n c e v a r i a b l e s */
private int minVal, maxVal;
/* c o n s t r u c t o r s */
/**
* pre: min <= init <= max
* post: this.countVal() == init &&
* this.minimumVal() == min && this.maximumVal() = max
*/
public RollOverCounter(int init, int min, int max) {
super(init); // explicit invocation of parent's constructor
minVal = min;
maxVal = max;
}
/**
* pre: min <= max
* post: this.countVal() == min &&
* this.minimumVal() == min && this.maximumVal() = max
*/
public RollOverCounter(int min, int max) { this(min, min, max); }
/* n e w o b s e r v e r s */
public int minimumVal() { return minVal; }
public int maximumVal() { return maxVal; }
/* o v e r r i d i n g m u t a t o r s */
/** pre: let c = this.countVal()
* post: this.countVal() == f(c), where
* f(k): if k == this.maximumVal() then this.minimumVal() else k+1
*/
public void increment() {
if (countVal() == maximumVal())
{ setTo(minVal); }
else
{ super.increment(); } // invocation of superclass's overridden method
}
/** pre: let c = this.countVal()
* post: this.countVal() == g(c), where
* g(k): if k == this.minimumVal() then this.maximumVal() else k-1
*/
public void decrement() {
if (countVal() == minimumVal())
{ setTo(maxVal); }
else
{ super.decrement(); } // invocation of superclass's overridden method
}
}
Notice that, within the class RollOverCounter, we have supplied new versions of the methods increment() and decrement(), rather than inheriting those methods from its parent, ResetableCounter (which itself inherited them from its parent, Counter). That is, RolloverCounter overrides its parent's methods increment() and decrement(). Note, however, as this example illustrates, that an overriding method in a subclass may invoke the method it overrides by inserting "super." before its name. It is common for an overridden method to be invoked within the method that overrides it; this makes sense, because often the overriding method carries out the same actions, plus possibly more, as the overridden method.
Now suppose we want a "stopping" counter that is similar to a "rollover" counter except that, where the latter would have rolled over, the former doesn't change. Suppose that we also want a "warning" counter that throws an exception in the same situations where the other two kinds of counters would have rolled over and stopped, respectively.
All three kinds of counters that were just mentioned ---rollover, stopping, and warning counters--- are similar; hence, rather than making the three corresponding classes be virtual copies of one another, we "factor out" their commonality and make an abstract class from which all three can be derived via extension. Because what is common about these three kinds of counters is that their values are bounded from below and above, we call this new class BoundedCounter:
public abstract class BoundedCounter extends ResetableCounter {
/* n e w i n s t a n c e v a r i a b l e s */
private int minVal, maxVal;
/* n e w c l a s s - w i d e c o n s t a n t s */
protected static final int defaultMinVal = 0;
protected static final int defaultMaxVal = 999;
/* c o n s t r u c t o r s */
/**
* pre: min <= init <= max
* post: this.countVal() == init &&
* this.minimumVal() == min && this.maximumVal() = max
*/
public BoundedCounter(int init, int min, int max) {
super(init);
minVal = min;
maxVal = max;
}
/**
* pre: min <= max
* post: this.countVal() == min &&
* this.minimumVal() == min && this.maximumVal() = max
*/
public BoundedCounter(int min, int max) { this(min, min, max); }
/**
* pre: true
* post: this.countVal() == defaultMinVal &&
* this.minimumVal() == defaultMinVal &&
* this.maximumVal() == defaultMaxVal
*/
public BoundedCounter() { this(defaultMinVal, defaultMaxVal); }
/* n e w o b s e r v e r s */
public int minimumVal() { return minVal; }
public int maximumVal() { return maxVal; }
/* o v e r r i d i n g m u t a t o r s */
/*
* pre: let c = countVal()
* post: if c != maximumVal(), then same as overridden counterpart;
* if c == maximumVal(), then determined by subclass's
* method incrementFromMax()
*/
public void increment() {
if (countVal() == maximumVal())
{ incrementFromMax(); }
else
{ super.increment(); }
}
/*
* pre: let c = countVal()
* post: if c != minimumVal(), then same as overridden counterpart;
* if c == minimumVal(), then determined by subclass's
* method decrementFromMin()
*/
public void decrement() {
if (countVal() == minimumVal())
{ decrementFromMin(); }
else
{ super.decrement(); }
}
// abstract methods that subclasses must define in order to
// fully describe how increment() and decrement() behave
protected abstract void incrementFromMax();
protected abstract void decrementFromMin();
}
Notice that BoundedCounter is declared to be abstract. This means that no object may be constructed directly from this class. Here, this makes sense, because even though the class provides its clients with a means of establishing boundaries upon a counter, it provides no specific means of enforcing them! More precisely, it assumes that a counter whose value is not at its maximum (respectively, minimum) allowed value should respond "normally" (i.e., as specified in its superclass, ResetableCounter) when incremented (respectively, decremented), but it yields to its subclasses the responsibility to define, via the method incrementFromMax() (respectively, decrementFromMin()) how a counter should respond to being incremented (respectively, decremented) when its value is at the maximum bound (respectively, minimum bound). This explains the presence of these two abstract methods.
Having defined BoundedCounter as above, the class RollOverCounter can be simplified to the following:
public class RollOverCounter extends BoundedCounter {
/* c o n s t r u c t o r s */
public RollOverCounter(int init, int min, int max)
{ super(init, min, max); }
public RollOverCounter(int min, int max) { super(min, max); }
public RollOverCounter() { super(); }
/* concrete definitions for abstract methods in superclass */
protected void incrementFromMax() { setTo(minimumVal()); }
protected void decrementFromMin() { setTo(maximumVal()); }
}
We leave as an exercise the problem of developing the classes StoppingCounter and WarningCounter.
To illustrate the concept of polymorphism, consider the following (rather contrived) method, which, given a String object and a Counter object, scans the characters in the former, invoking the latter's increment() method each time a space is encountered.
public static void countSpaces(String str, Counter counter) {
for (int i=0; i!= str.length(); i++) {
if (str.charAt(i) == ' ') {
counter.increment();
}
}
}
Consider the following code:
String s = "The cat in the hat ate a rat.";
Counter cntr1 = new Counter(0);
countSpaces(s, cntr1);
System.out.println("cntr1 says " + cntr1.countVal());
Counter cntr2 = new RollOverCounter(0,0,3);
countSpaces(s, cntr2);
System.out.println("cntr2 says " + cntr2.countVal());
Counter cntr3 = new StoppingCounter(0,0,3);
countSpaces(s, cntr3);
System.out.println("cntr3 says " + cntr3.countVal());
Counter cntr4 = new WarningCounter(0,0,3);
countSpaces(s, cntr4);
System.out.println("cntr4 says " + cntr4.countVal());