Extending an abstract Class
A class might choose the design strategy with abstract methods to dictate certain behavior, but allow its subclasses the freedom to provide the relevant implementation. An abstract class forces its subclasses to provide the subclass-specific functionality stipulated by its abstract methods, which is needed to fully implement its abstraction. In other words, subclasses of the abstract class have to take a stand and provide implementations of any inherited abstract methods before objects can be created. In Example 5.8, since the class Light is abstract, it forces its concrete (i.e., non-abstract) subclass to provide an implementation for the abstract method energyCost(). The concrete subclass TubeLight provides an implementation for this method at (3).
class TubeLight extends Light {
// …
// Implementation of the abstract method from the superclass.
@Override public double energyCost(int noOfHours) { // (3)
return 0.15 * noOfHours;
}
}
Creating an object of a subclass results in the fields of all its superclasses, whether these classes are abstract or not, to be created and initialized—that is, they exist in the subclass object. As with normal classes, the inheritance relationship between classes allows references of the abstract superclass type to be declared and used to refer to objects of their subclasses—that is, these references exhibit polymorphic behavior (p. 278).
Figure 5.3 Class Diagram for Example 5.8
In Example 5.8, the class Factory creates an instance of the subclass TubeLight at (6). The private fields declared in the abstract superclass Light can be accessed indirectly by invoking the public methods it provides on objects of the subclass TubeLight. The subclass reference cellarLight is used to invoke public methods in the superclass in the following code:
TubeLight cellarLight = new TubeLight(18, true, “Cellar”, 590, 26); // (6)
cellarLight.switchOff(); // Method in superclass
System.out.println(cellarLight.isOn()); // Method in superclass: false
The subclass reference cellarLight of course can be used to invoke subclass-specific methods, as shown at (7).
System.out.printf(“Energy cost ($): %2.2f%n”,
cellarLight.energyCost(40)); // (7) Using subclass reference
References of an abstract superclass can be declared and assigned reference values of its subclass objects, as shown at (8). Superclass references can be used to manipulate subclass objects, as shown at (9), where the energyCost() method from the subclass TubeLight is executed.
Light nightLight = new TubeLight(15, false, “Bedroom”, 850, 15); // (8)
System.out.printf(“Energy cost ($): %2.2f%n”,
nightLight.energyCost(30)); // (9) Using superclass reference
// Invokes method in subclass
// Requires throws clause at (4)
Note that using the subclass reference cellarLight at (7) to invoke the method energyCost() cannot throw a checked exception, as readily seen from its declaration in the subclass TubeLight. However, using the superclass reference nightLight at (9) to invoke the method energyCost() can throw a checked exception, as seen from the method declaration in the superclass Light. At compile time, only the static type of the reference is known, namely Light, and the method energyCost() in this class throws a checked InvalidHoursException (§7.2, p. 374). The throws clause in the main() method at (4) specifies this exception—otherwise, the code will not compile.
In the code below, the class AbstractArt at (2) must be declared as abstract as it does not implement the abstract method paint() from its superclass Art at (1).
abstract class Art { abstract void paint(); } // (1) Abstract class
abstract class AbstractArt extends Art {} // (2) Must be abstract
class MinimalistArt extends AbstractArt { // (3) Concrete class
@Override void paint() { System.out.println(“:-)”); } // (4) Concrete method
}
abstract class PostModernMinimalistArt
extends MinimalistArt { // (5) Abstract class
@Override void paint() { System.out.println(“:-(“); } // (6) Concrete method
// overrides (4)
}
class ArtsyFartsy extends PostModernMinimalistArt {} // (7) Concrete class
Analogous to a normal class, an abstract class can only extend a single non-final class that can be either concrete or abstract. In the code above, the abstract class AbstractArt at (2) extends the abstract class Art, and the abstract class PostModernMinimalistArt at (5) extends the concrete class MinimalistArt.
A non-final concrete class, which by definition has no abstract methods, can be considered incomplete by declaring it as abstract. The PostModernMinimalistArt class at (5) is declared abstract and considered incomplete, even though it is concrete. It cannot be instantiated. However, its subclass ArtsyFartsy at (7) is a concrete class, as it inherits the concrete method paint() from its abstract superclass PostModernMinimalistArt.
A class cannot be declared both final and abstract—that would be a contradiction in terms: A final class cannot be extended, but an abstract class is incomplete or considered to be incomplete and must be extended.
An abstract class should not be used to implement a class that cannot be instantiated. The recommended practice is to only provide a zero-argument constructor that is private, thus making sure that it is never invoked in the class.
In many ways abstract classes and interfaces are similar, and interfaces can be used with advantage in many cases. However, if private state should be maintained with instance members, then abstract classes are preferred, as interfaces do not have any notion of state.
Analogous to a normal class, an abstract class can implement multiple interfaces (p. 240).
Example 5.8 Using Abstract Classes
// File: Factory.java
// Checked exceptions:
class InvalidHoursException extends Exception {}
class NegativeHoursException extends InvalidHoursException {}
class ZeroHoursException extends InvalidHoursException {}
abstract class Light { // (1) Abstract class
// Fields:
private int noOfWatts; // Wattage
private boolean indicator; // On or off
private String location; // Placement
// Non-zero argument constructor:
Light(int noOfWatts, boolean indicator, String location) {
this.noOfWatts = noOfWatts;
this.indicator = indicator;
this.location = location;
}
// Instance methods:
public void switchOn() { indicator = true; }
public void switchOff() { indicator = false; }
public boolean isOn() { return indicator; }
// Abstract instance method:
protected abstract double energyCost(int noOfHours) // (2) Method header
throws InvalidHoursException; // No method body
}
//______________________________________________________________________________
class TubeLight extends Light {
// Instance fields:
private int tubeLength; // millimeters
private int tubeDiameter; // millimeters
// Non-zero argument constructor
TubeLight(int noOfWatts, boolean indicator, String location,
int tubeLength, int tubeDiameter) {
super(noOfWatts, indicator, location); // Calling constructor in superclass.
this.tubeLength = tubeLength;
this.tubeDiameter = tubeDiameter;
}
// Implementation of the abstract method from the superclass.
@Override public double energyCost(int noOfHours) { // (3)
return 0.15 * noOfHours;
}
}
//______________________________________________________________________________
public class Factory {
public static void main(String[] args) throws InvalidHoursException { // (4)
// Light porchLight = new Light(21, true, “Porch”); // (5) Compile-time error!
TubeLight cellarLight = new TubeLight(18, true, “Cellar”, 590, 26); // (6)
cellarLight.switchOff();
System.out.println(cellarLight.isOn()); // false
System.out.printf(“Energy cost ($): %2.2f%n”,
cellarLight.energyCost(40)); // (7) Using subclass reference
Light nightLight = new TubeLight(15, false, “Bedroom”, 850, 15); // (8)
System.out.printf(“Energy cost ($): %2.2f%n”,
nightLight.energyCost(30)); // (9) Using superclass reference
// Invokes method in subclass
// Requires throws clause in (4)
}
}
Output from the program:
false
Energy cost ($): 6.00
Energy cost ($): 4.50