Unit 17: Abstract Class
Learning Objectives
Students should
- be familiar with the concept of an abstract class.
- know the use of the Java keyword
abstract
and the constraints that come with it. - understand the usefulness of defining and using an abstract class.
- understand what makes a class concrete.
High-Level Abstraction
Recall that the concept of abstraction involves hiding away unnecessary complexity and details so that programmers do not have to bogged down with the nitty-gritty. That's why we abstract a real-world object into a class with only fields and methods because we only want to focus on specific behavior of the object. For instance, we abstract a circle into a point and a radius such that we can only check if another point is contained within the circle or not via Circle::contains
method. While a circle may also be transformed, as long as we do not have such methods, the behavior is not captured by our abstracted circle.
When we code, we should, as much as possible, try to work with the higher-level abstraction, rather than the detailed version. Following this principle would allow us to write code that is general and extensible, by taking full advantage of inheritance and polymorphism.
Take the following example which you have seen,
contains v0.1 | |
---|---|
1 2 3 4 5 6 7 8 |
|
The function above is very general. We do not assume and do not need to know, about the details of the items being stored or search. All we required is that the equals
method compared if two objects are equal.
In contrast, someone whose mind focuses on finding a circle, might write something like this:
contains v0.1 | |
---|---|
1 2 3 4 5 6 7 8 |
|
The version above serves the purpose, but is not general enough. The only method used is equals
, which Circle
inherits/overrides from Object
so that using Circle
for this function is too constraining. We can reuse this for any other subclasses of Circle
, but not other classes.
Abstracting Circles
Now, let's consider the following function, which finds the largest area among the circles in a given array:
findLargest v0.1 | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
findLargest
suffers from the same specificity as the version 0.3 of contains
. It only works for Circle
and its subclasses only. Can we make this more general? We cannot replace Circle
with Object
,
findLargest v0.2 | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
since getArea
is not defined for a generic object (e.g., what does getArea
of a string mean?).
To allow us to apply findLargest
to a more generic object, we have to create a new type -- something more specific than Object
that supports getArea()
, yet more general than Circle
. After all, we can have a Square
that has an area.
Shape
Let's create a new class called Shape
, and redefine our Circle
class as a subclass of Shape
. We can now create other shapes, Square
, Rectangle
, Triangle
, etc, and define the getArea
method for each of them.
With the new Shape
class, we can rewrite findLargest
as:
findLargest v0.3 | |
---|---|
1 2 3 4 5 6 7 8 9 10 |
|
This version not only works for an array of Square
, Rectangle
, Circle
, etc but also an array containing multiple shapes!
Let's actually write out our new Shape
class:
1 2 3 4 5 |
|
and rewrite our Circle
:
Circle v0.8 | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
|
Notably, since our Shape
is a highly abstract entity, it does not have any fields. One question that arises is, how are we going to write Shape::getArea()
? We cannot compute the area of a shape unless we know what sort of shape it is.
One solution is make Shape::getArea()
returns 0.
1 2 3 4 5 |
|
This is not ideal. It is easy for someone to inherit from Shape
, but forget to override getArea()
. If this happens, then the subclass will have an area of 0. Bugs ensue.
As we usually do in CS2030S, we want to exploit programming language constructs and the compiler to check and catch such errors for us.
Abstract Methods and Classes
This brings us to the concept of abstract classes. An abstract class in Java is a class that has been made into something so general that it cannot and should not be instantiated. Usually, this means that one or more of its instance methods cannot be implemented without further details.
Abstract Class
An abstract class is a class that cannot be instantiated. If a class has at least one abstract method, it must be declared as an abstract class with the keyword abstract
in the class declaration.
Note that an array of abstract class may still be created.
1 |
|
The Shape
class above makes a good abstract class since we do not have enough details to implement Shape::getArea
.
To declare an abstract class in Java, we add the abstract
keyword to the class
declaration. To make a method abstract, we add the keyword abstract
when we declare the method.
An abstract
method cannot be implemented and therefore should not have any method body (i.e., no { .. }
). Instead, it ends with a semi-colon (i.e., ;
).
This is how we implement Shape
as an abstract class.
1 2 3 |
|
An abstract class cannot be instantiated. Any attempt to do so, such as:
1 |
|
would result in a compilation error.
1 2 3 4 |
|
Note that our simple example of Shape
only encapsulates one abstract method. An abstract class can contain multiple fields and multiple methods. Not all the methods have to be abstract. As long as one of them is abstract, the class becomes abstract.
To illustrate this, consider
1 2 3 4 5 6 7 8 9 |
|
Shape::isSymmetric
is a concrete method but the class is still abstract since Shape::getArea()
is abstract.
Rule for Abstract Class
Note that the rule for abstract class is not symmetric.
A class with at least one abstract method must be declared abstract.
On the other hand,
An abstract class may have no abstract method.
Concrete Classes
We call a class that is not abstract as a concrete class. A concrete class cannot have any abstract method. Thus, any subclass of Shape
must override getArea()
to supply its own implementation.
Class Diagram (Part 5)
The class diagram for an abstract class is simple, we simply denote the class with <<abstract>>
to indicate that the class is abstract. As for abstract methods, we write the method in italics.
Consider the classes Shape
, Circle
, and Square
such that:
Shape
is an abstract class:- Has a single abstract method
double getArea()
- Has a single abstract method
Circle
is a concrete class that is a subclass ofShape
:- Has two private fields:
Point c
(i.e., the center) anddouble r
(i.e., the radius) - Has two public concrete methods:
boolean contains(Point p)
anddouble getArea()
- Has two private fields:
Square
is a concrete class that is a subclass ofShape
:- Has two private fields:
Point tl
(i.e., the top-left point) anddouble s
(i.e., the sides) - Has two public concrete methods:
boolean contains(Point p)
anddouble getArea()
- Has two private fields:
The class diagram looks like the following: