Class modifiers
Just as with fields and methods, classes can be modified to change their access or behavior.
final: If you declare a class final, it cannot be extended. String, for example, is final.
abstract: Abstract classes exist ONLY to be superclasses. You cannot create an object of that type directly.
Let's consider the case of a Student. Students from different schools have many things in common with each other, and many others that are unique to their school. It would make sense to have a parent class Student that holds common data points (e.g. name, school name, GPA, etc.) and child classes to hold school-specific information (e.g. alpha code, obstacle-course scores, etc.)
Our first abstract class
public abstract class Student {
protected String school;
protected String name;
protected int id;
protected float gpa;
public Student(String n, String s) {
name = n;
school = s;
setID();
}
protected abstract void setID();
public String toString() {
// Object.getClass() returns the name of the object's class
return String.format("%8d %10s %15s (%s)", id, name, school, getClass());
}
}
Some items to note:
- The abstract keyword means that the class cannot be instantiated, so Student s = New Student("Bob", "PCU"); would fail to compile
- The entire class must be declared abstract because a single member is abstract
- The constructor can call setID() even though it is not yet defined
- Object.getClass() returns the plain-text name of the Object's class
Our first final classes
public final class Mid extends Student {
private static int nextAlpha = 6;
public Mid(String n) {
super(n, "USNA");
}
protected void setID() {
id = 170000 + nextAlpha;
nextAlpha+=6;
}
protected String schoolCheer() {
return "Go Navy";
}
protected void driveYP() {
System.out.println("Full speed ahead");
}
public static void main(String[] args) {
Mid[] marray = new Mid[5];
marray[0] = new Mid("Alice");
marray[1] = new Mid("Bob");
marray[2] = new Mid("Charlie");
marray[3] = new Mid("Dave");
marray[4] = new Mid("Eve");
for (int i=0; i<marray.length; i++) {
System.out.println(marray[i]);
}
}
}
|
public final class Cadet extends Student {
private static int nextCadetID = 1;
public Cadet(String n) {
super(n, "West Point");
}
protected void setID() {
id = 20170000 + nextCadetID;
nextCadetID+=1;
}
protected String schoolCheer() {
return "Go Army";
}
protected void driveTank() {
System.out.println("What does this lever do?");
}
public static void main(String[] args) {
Cadet[] carray = new Cadet[5];
carray[0] = new Cadet("Albert");
carray[1] = new Cadet("Billy");
carray[2] = new Cadet("Carol");
carray[3] = new Cadet("Don");
carray[4] = new Cadet("Eddie");
for (int i=0; i<carray.length; i++) {
System.out.println(carray[i]);
}
}
}
|
Some items to note:
- The final keyword means that the class cannot be extended, so public class Plebe extends Mid would fail to compile
- This class must have a protected void setID() because it was declared abstract in the parent
- The Student's constructor will call Mid's or Cadet's setID() method
What can Polymorphism do for me?
So all of this inheritance and abstract classes stuff seems vaguely helpful, but why all the excitement? Is this really worth a whole six weeks? Yes. Polymorphism is part of the reason why.
Polymorphism says that if you declare a pointer to a particular type, you can make it point to an object of that type or of any sub-type of that type. Thus I can say:
Student s = new Mid();
without error. The ramifications of this are substantial.
Most of the time you won't do just that, but instead you will create a group of objects, each of a different type with the same super-type:
public class TestStudents {
public static void main(String[] args) {
Student[] sarray = new Student[10];
sarray[0] = new Mid("Alice");
sarray[1] = new Mid("Bob");
sarray[2] = new Mid("Charlie");
sarray[3] = new Mid("Dave");
sarray[4] = new Mid("Eve");
sarray[5] = new Cadet("Albert");
sarray[6] = new Cadet("Billy");
sarray[7] = new Cadet("Carol");
sarray[8] = new Cadet("Don");
sarray[9] = new Cadet("Eddie");
System.out.println("List of items from Student[]");
System.out.println("----------------------------");
for (int i=0; i<sarray.length; i++) {
System.out.println(sarray[i]);
}
}
}
Output:
List of items from Student[] ---------------------------- 170006 Alice USNA (class Mid) 170012 Bob USNA (class Mid) 170018 Charlie USNA (class Mid) 170024 Dave USNA (class Mid) 170030 Eve USNA (class Mid) 20170001 Albert West Point (class Cadet) 20170002 Billy West Point (class Cadet) 20170003 Carol West Point (class Cadet) 20170004 Don West Point (class Cadet) 20170005 Eddie West Point (class Cadet)
Some items to note:
- Object.getClass() returns either "class Mid" or "class Cadet", even though it is called from Student.toString()
- The two setID() methods created different formats for the id
Now all the different object that can do Student things are in one place.
More importantly, though, given an arbitrary element of sarray, we are guaranteed that it has certain members. Each of these, because they have Student as a superclass, has a name, school, id, gpa, and schoolCheer(). So, we can do:
for (int i=0; i<sarray.length; i++)
System.out.println(sarray[i].schoolCheer());
}
The final important observation here is, not only do things just work, without having lots of if/then bits to them, but it factors the code amongst the classes. Without polymorphism, the loop might look like this:
for (int i=0; i<sarray.length; i++) {
if(sarray[i].school.equals("USNA")) {
System.out.println("Go Navy");
}
else if(sarray[i].school.equals("West Point")) {
System.out.println("Go Army");
}
}
We can dispense with all of that code, scattering it among each of the individual classes. The code doesn't live in a single place, it lives with each class. The main focus of the code is no longer on finding the correct method to call, but on the various classes/objects.
What can Polymorphism not do for me?
Polymorphism does not let you easily reach methods that exist in the subclass but not the superclass. See if you can understand why:
Call unknown methods in a subclass:
GOOD:
Student s = new Mid("Alice");
System.out.println(s.name);
BAD:
Student s = new Mid("Alice");
System.out.println(s.driveYP());
Two workarounds:
Student s = new Mid("Alice");
((Mid)s).driveYP() ;
Mid m = new Mid("Alice");
m.driveYP();