Here's a passage from my book, Beginning Java Objects, which explains abstract classes (taken from Chapter 7 - copyright 2000 Jacquie Barker) - I hope it helps!
(Unfortunately, when I pasted in the excerpt from the book, the indentation of all code examples was lost ...)
Abstract Classes
We learned in Chapter 5 how useful it can be to consolidate shared features - attributes and methods - of two or more classes into a common superclass, a process known as generalization. We did this when we created the Person class as a generalization of Student and Professor and then moved the declarations of all of their common attributes (name, address, birthdate) and methods (the 'get' and 'set' methods for each of the shared attributes) into this superclass. By doing so, the Student and Professor subclasses both became simpler, and we eliminated a lot of redundancy that would otherwise have made maintenance of the SRS much more cumbersome.
There is one potential dilemma to be dealt with when creating a superclass after the fact: if all of the classes that are to be subclasses of this new superclass happen to have defined methods with the same signature, but with different logic, which version of the method, if any, should be propagated up to the parent? Let's use a specific example: say that long before it occurred to us to create a Person superclass, a requirement arose to devise a method for both the Student and Professor classes to compute their respective monthly salaries (we are assuming that students work for the university as teaching assistants or as research fellows). We decided that the signature of this method would be as follows:
public float computePaycheck(int hoursWorked);
o In the case of a professor, his/her salary is based on the number of hours worked in a given month multiplied by an hourly wage.
o In the case of a student, his/her salary is a fixed monthly stipend, but is only paid if the student worked more than a certain minimum number of hours during the month.
We then implemented the computePaycheck() methods for these two classes, the code or which is shown below:
class Professor {
String name;
String ssn;
float hourlySalary;
// etc.
// Get/set methods omitted ...
public float computePaycheck(int hoursWorked) {
return hoursWorked * hourlySalary;
}
}
class Student {
String name;
String ssn;
float monthlyStipend;
int minimumRequiredHours;
// etc.
// Get/set methods omitted ...
// Same signature as for the Professor class, but different
// internal logic.
public float computePaycheck(int hoursWorked) {
if (hoursWorked > minimumRequiredHours)
return monthlyStipend;
else return 0.0;
}
}
At some later point in time, we decide to generalize these two classes into a common superclass called Person. We can easily move the common attributes (name, ssn) and their get/set methods out of Student and Professor and into Person, but what should we do about the computePaycheck() method?
o We could provide a generic computePaycheck() method with the same exact signature in Person, and then allow the Professor and Student classes' specialized versions of the method to effectively override it. But, this raises the question, why even bother to code the internal processing details of a computePaycheck() method in Person if both Student and Professor have already in essence overridden it? The answer to this question depends on whether you plan on instantiating the Person class directly. That is,
o If you plan on creating objects of generic type Person in your application - objects which are neither Students nor Professors - then the Person class may indeed need a computePaycheck() method of its own, to perform some sort of generic paycheck computation.
q On the other hand, if you only plan on instantiating the Student and Professor classes, then going to the trouble of programming the internal processing details of a method for Person that will never get used does indeed seems like a waste of time.
o If we know that we are not going to instantiate Person objects, then another option would be to leave the computePaycheck() method out of the Person class entirely. Both Student and Professor would then be adding this method as a new feature above and beyond what they inherit from Person, versus inheriting and overriding the method. The shortcoming of this approach is that we lose the ability to guarantee that all future Person objects will understand the message for computing their paychecks. If another subclass of Person is created in the future (say, AdministrativeStaffMember), and the designer of that class isn't aware of the need to provide for a computePaycheck() method, then we'll wind up with a situation where some Person objects will understand a message like:
p.computePaycheck(40);
and others will not. We'll therefore lose the advantage of
polymorphism: namely, being able to create a collection of all different types of Person objects, and to iterate through it to compute their paychecks, because even though an AdministrativeStaffMember is a Person, it doesn't know how to respond to a request to perform this service.
Before we try to make a final decision on how to approach the computePaycheck() method, let's consider a different set of circumstances.
The preceding example explored a situation where the need for generalization arose after the fact; now let's look at this problem from the opposite perspective. Say that we have the foresight to know that we are going to need various types of Course objects in our SRS: lecture courses, lab courses, independent study courses, etc. So, we want to start out on the right foot by designing the Course (super)class to be as versatile as possible to facilitate future specialization.
We might determine up front that all Courses, regardless of type, are going to need to share a few common attributes:
String courseName;
String courseNumber;
int creditValue;
CollectionType enrolledStudents;
Professor instructor;
as well as a few common behaviors:
establishCourseSchedule()
enrollStudent()
assignProfessor()
Some of these behaviors may be generic enough so that we can afford to program them in detail for the Course class, knowing that it is a pretty safe bet that any future subclasses of Course will inherit these methods 'as is' without needing to override them:
class Course {
String courseName;
String courseNumber;
int creditValue;
Collection enrolledStudents;
Professor instructor;
// Get/set methods omitted ...
public boolean enrollStudent(Student s) {
if (we haven't exceeded the maximum allowed enrollment)
enrolledStudents.add (s);
}
public void assignInstructor(Professor p) {
instructor = p;
}
}
However, other of the behaviors may be too specialized to enable us to come up with a useful generic version. For example, the rules governing how to schedule class meetings may be class-specific:
o A lecture course may only meet once a week for 3 hours at a time;
o A lab course may meet twice a week for 2 hours each time; and
o An independent study course may meet on a custom schedule agreed to by a given student and professor.
So, it would be a waste of time for us to bother trying to program a generic version of the establishCourseSchedule() method at this point in time. And yet we know that we'll need such a method to be programmed for all subclasses of Course that get created down the road. How do we communicate the requirement for such a behavior in all subclasses of Course and, more importantly, enforce its future implementation?
OO languages such as Java come to the rescue with the concept of abstract classes. An abstract class is used to enumerate the required behaviors of a class without having to provide an explicit implementation of each and every such behavior. We program an abstract class in much the same way that we program a regular class, with one exception: for those behaviors for which we cannot (or care not to) devise a generic implementation - e.g., the establishCourseSchedule() method in our preceding example - we are allowed to specify method signatures without having to program the method bodies. We refer to a 'codeless', or signature-only, method specification as an abstract method.
Beginners often confuse the term 'abstract class' with 'abstract data type' because the terms sound the same. But, ALL classes - abstract or not - are abstract data types, whereas only certain classes are abstract classes.
So, we can go back to our Course class definition, and add an abstract method as highlighted below:
class Course {
String courseName;
String courseNumber;
int creditValue;
CollectionType enrolledStudents = new CollectionType ();
Professor instructor;
public boolean enrollStudent(Student s) {
if (we haven't exceeded the maximum allowed enrollment)
enrolledStudents.add(s);
}
public void assignInstructor(Professor p) {
instructor = p;
}
// Note the use of the 'abstract' keyword.
public abstract void establishCourseSchedule (String
startDate, String endDate);
}
Note that the establishCourseSchedule() method signature has no curly braces following the closing parenthesis of the argument list. Instead, the signature ends with a semicolon (

- that is, it is missing its 'body', which normally contains the detailed logic of how the method is to be performed. The method signature is explicitly labeled as 'abstract', to notify the compiler that we didn't accidentally forget to program this method, but rather that we knew what we were doing when we intentionally omitted the body.
By specifying an abstract method, we have:
o Specified a service that objects belonging to this class (or one of its subclasses) must be able to perform;
o Detailed the means by which we will ask them to perform this service by providing a method signature, which as we learned in Chapter 4 controls the format of the message that we will pass to such objects when we want them to perform the service; and
o Facilitated polymorphism - at least with respect to a certain method - by ensuring that all subclasses of Course will indeed recognize a message associated with this method.
However, we have done so without pinpointing the private details of how the method will accomplish this behavior. That is, we've specified 'what' an object belonging to this class needs to be able to do without pinning down 'how' it is to be done.
Whenever a class contains one or more abstract methods, then the class as a whole is considered to be an abstract class. In Java, we designate that a class is abstract with the keyword 'abstract':
abstract class Course {
// details omitted
}
It isn't necessary for all methods in an abstract class to be abstract; an abstract class can also contain 'normal' methods that have both a signature and a body, known as concrete methods.
Abstract Classes and Instantiation
There is one caveat with respect to abstract classes: they cannot be instantiated. That is, if we define Course to be an abstract class in the SRS, then we cannot ever instantiate generic Course objects in our application. This makes intuitive sense:
o If we could create an object of type Course, it would then be expected to know how to respond to a message to establish a course schedule, because the Course class defines a method signature for this behavior.
o But because there is no code behind that method signature, the Course object would not know how to behave in response to such a message.
The compiler comes to our assistance by preventing us from even writing code to instantiate an abstract object in the first place; if we were to try to compile the following code snippet:
Course c = new Course(); // Impossible! The compiler will
// generate an error
// on this line of code.
// details omitted ...
// Behavior undefined!
c.establishCourseSchedule('01/10/2001', '05/15/2001');
we'd get the following compilation error on the first line:
class Course is an abstract class. It can't be instantiated.
While we are indeed prevented from instantiating an abstract class, we are nonetheless permitted to declare reference variables to be of an abstract type:
Course x; // This is OK.
x = new Course(); // Error - Course is an abstract class!
Why would we even want to declare reference variables of type Course in the first place if we can't instantiate objects of type Course? It has to do with supporting polymorphism; we'll learn the importance of being able to define reference variables of an abstract type when we talk about Java collections in depth in Chapter 13.
Inheritance and Abstract Classes
An abstract class may be extended to create subclasses, in the same way that a 'normal' class can be extended. Having intentionally created Course as an abstract class to serve as a common 'template' for all of the various course types we envision needing for the SRS, we may later derive subclasses LectureCourse, LabCourse, and IndependentStudyCourse. Unless a subclass overrides all of the abstract methods of its abstract superclass with concrete methods, however, it will automatically be considered abstract, as well. That is, a subclass of an abstract class must provide an implementation for all inherited abstract methods in order to 'break the spell' of being abstract.
In the following code snippet, we show these three subclasses of Course; of these, only two - LectureCourse and LabCourse - provide implementations of the establishCourseSchedule() method, and so the third subclass - IndependentStudyCourse - remains an abstract class and hence cannot be instantiated.
class LectureCourse extends Course {
// All attributes are inherited from the Course class;
// no new attributes are
// added.
public void establishCourseSchedule (String startDate,
String endDate) {
// Logic would be provided here for how a lecture course
// establishes a course schedule; details omitted ...
}
}
class LabCourse extends Course {
// All attributes are inherited from the Course class; no
// new attributes are
// added.
public void establishCourseSchedule (String startDate,
String endDate) {
// Logic would be provided here for how a lab course
// establishes a
// course schedule; details omitted ...
}
}
class IndependentStudyCourse extends Course {
// All attributes are inherited from the Course class;
// no new attributes are
// added, and we don't override the establishCourseSchedule()
// method in this subclass, either.
}
If we were to try to compile the above code, the Java compiler would force us to flag the IndependentStudyCourse class with the keyword 'abstract'; that is, we'd get the following compilation error:
Class IndependentStudyCourse must be declared abstract. It does not define void establishCourseSchedule (String, String) from class Course.
We've just hit upon how abstract methods serve to enforce implementation requirements! Declaring an abstract method in a superclass ultimately forces all derived subclasses to provide class-specific implementations of the abstract methods; otherwise, the subclasses cannot be instantiated.
------------------
author of:
Beginning Java Objects [This message has been edited by Jacquie Barker (edited January 01, 2001).]