An abstract class cannot be used to create objects. An abstract class can contain abstract methods, which are implemented in concrete subclasses. In the inheritance hierarchy, classes become more specific and concrete with each new subclass. If you move from a subclass back up to a superclass, the classes become more general and less specific. Class design should ensure that a superclass contains common features of its
subclasses. Sometimes a superclass is so abstract that it cannot be used to create any specific instances. Such a class is referred to as an abstract class.
In this post, GeometricObject was defined as the superclass for Circle and Rectangle. GeometricObject models common features of geometric objects. Both Circle and Rectangle contain the getArea() and getPerimeter() methods for computing the area and perimeter of a circle and a rectangle. Since you can compute areas and perimeters for all geometric objects, it is better to define the getArea() and getPerimeter() methods in the GeometricObject class. However, these methods cannot be implemented in the GeometricObject class, because their implementation depends on the specific type of geometric object. Such methods are referred to as abstract methods and are denoted using the abstract modifier in the method header. After you define the methods in GeometricObject, it becomes an abstract class. Abstract classes are denoted using the abstract modifier in the class header. In UML graphic notation, the names of abstract classes and their abstract methods are italicized, as shown in progeam below. The program gives the source code for the new GeometricObject class.
package demo;
public abstract class GeometricObject {
private String color = "white";
private boolean filled;
private java.util.Date dateCreated;
/** Construct a default geometric object */
protected GeometricObject() {
dateCreated = new java.util.Date();
}
/** Construct a geometric object with color and filled value */
protected GeometricObject(String color, boolean filled) {
dateCreated = new java.util.Date();
this.color = color;
this.filled = filled;
}
/** Return color */
public String getColor() {
return color;
}
/** Set a new color */
public void setColor(String color) {
this.color = color;
}
/** Return filled. Since filled is boolean, the get method is named isFilled */
public boolean isFilled() {
return filled;
}
/** Set a new filled */
public void setFilled(boolean filled) {
this.filled = filled;
}
/** Get dateCreated */
public java.util.Date getDateCreated(){
return dateCreated;
}
@Override
public String toString() {
return "created on " + dateCreated + "\ncolor: " + color + " and filled: " + filled;
}
/** Abstract method getArea */
public abstract double getArea();
/** Abstract method getPerimeter */
public abstract double getPerimeter();
}
Abstract classes are like regular classes, but you cannot create instances of abstract classes using the new operator. An abstract method is defined without implementation. Its implementation is provided by the subclasses. A class that contains abstract methods must be defined as abstract.
The constructor in the abstract class is defined as protected, because it is used only by subclasses. When you create an instance of a concrete subclass, its superclass’s constructor is invoked to initialize data fields defined in the superclass.
The GeometricObject abstract class defines the common features (data and methods) for geometric objects and provides appropriate constructors. Because you don’t know how to compute areas and perimeters of geometric objects, getArea() and getPerimeter() are defined as abstract methods. These methods are implemented in the subclasses. The implementation of Circle and Rectangle is the same as in the programs below, except that they extend the GeometricObject class defined.
package demo;
public class Circle extends GeometricObject {
private double radius;
public Circle() {}
public Circle(double radius) {
this.radius = radius;
}
public Circle(double radius, String color, boolean filled) {
this.radius = radius;
setColor(color);
setFilled(filled);
}
/** Return radius */
public double getRadius() {
return radius;
}
/** Set a new radius */
public void setRadius(double radius) {
this.radius = radius;
}
/** Return area */
public double getArea() {
return radius * radius * Math.PI;
}
/** Return diameter */
public double getDiameter() {
return 2 * radius;
}
/** Return perimeter */
public double getPerimeter() {
return 2 * radius * Math.PI;
}
/** Print the circle info */
public void printCircle() {
System.out.println("The circle is created " + getDateCreated() + " and the radius is " + radius);
}
}
package demo;
public class Rectangle extends GeometricObject {
private double width;
private double height;
public Rectangle() {}
public Rectangle(double width, double height) {
this.width = width;
this.height = height;
}
public Rectangle(double width, double height, String color, boolean filled) {
this.width = width;
this.height = height;
setColor(color);
setFilled(filled);
}
/** Return width */
public double getWidth() {
return width;
}
/** Set a new width */
public void setWidth(double width) {
this.width = width;
}
/** Return height */
public double height() {
return height;
}
/** Set a new height */
public void setHeight(double height) {
this.height = height;
}
/** Return area */
public double getArea() {
return width * height;
}
/** Return perimeter */
public double getPerimeter() {
return 2 * (width + height);
}
}
Why Abstract Methods?
You may be wondering what advantage is gained by defining the methods getArea() and getPerimeter() as abstract in the GeometricObject class. The example in Listing 13.4 shows the benefits of defining them in the GeometricObject class. The program creates two geometric objects, a circle and a rectangle, invokes the equalArea method to check whether they have equal areas, and invokes the displayGeometricObject method to display them.
The methods getArea() and getPerimeter() defined in the GeometricObject class are overridden in the Circle class and the Rectangle class. The statements (lines 7–8)
GeometricObject geoObject1 = new Circle(5);
GeometricObject geoObject2 = new Rectangle(5, 3);
create a new circle and rectangle and assign them to the variables geoObject1 and geoObject2. These two variables are of the GeometricObject type.
When invoking equalArea(geoObject1, geoObject2) (line 10), the getArea() method defined in the Circle class is used for object1.getArea(), since geoObject1 is a circle, and the getArea() method defined in the Rectangle class is used for object2.getArea(), since geoObject2 is a rectangle.
Similarly, when invoking displayGeometricObject(geoObject1) (line 13), the methods getArea() and getPerimeter() defined in the Circle class are used, and when invoking displayGeometricObject(geoObject2) (line 16), the methods getArea and getPerimeter defined in the Rectangle class are used. The JVM dynamically determines which of these methods to invoke at runtime, depending on the actual object that invokes the method.
Note that you could not define the equalArea method for comparing whether two geometric objects have the same area if the getArea method were not defined in GeometricObject. Now you have seen the benefits of defining the abstract methods in GeometricObject.
Interesting Points about Abstract Classes
The following points about abstract classes are worth noting:
- An abstract method cannot be contained in a nonabstract class. If a subclass of an abstract superclass does not implement all the abstract methods, the subclass must be defined as abstract. In other words, in a nonabstract subclass extended from an abstract class, all the abstract methods must be implemented. Also note that abstract methods are nonstatic.
- An abstract class cannot be instantiated using the new operator, but you can still define its constructors, which are invoked in the constructors of its subclasses. For instance, the constructors of GeometricObject are invoked in the Circle class and the Rectangle class.
- A class that contains abstract methods must be abstract. However, it is possible to define an abstract class that doesn’t contain any abstract methods. In this case, you cannot create instances of the class using the new operator. This class is used as a base class for defining subclasses.
- A subclass can override a method from its superclass to define it as abstract. This is very unusual, but it is useful when the implementation of the method in the superclass becomes invalid in the subclass. In this case, the subclass must be defined as abstract.
- A subclass can be abstract even if its superclass is concrete. For example, the Object class is concrete, but its subclasses, such as GeometricObject, may be abstract.
- You cannot create an instance from an abstract class using the new operator, but an abstract class can be used as a data type. Therefore, the following statement, which creates an array whose elements are of the GeometricObject type, is correct.
GeometricObject[] objects = new GeometricObject[10];
You can then create an instance of GeometricObject and assign its reference to the array like this:
objects[0] = new Circle();