Next: None. Up: Abstract classes. Previous: None.


Abstract classes

Textbook: Section 11.6

Sometimes classes pop up, where you don't really want to allow somebody to create an instance - you're only defining the class so that subclasses can inherit from it.

The Account class is actually a reasonable example of this. Nobody actually goes to the bank and opens up just an account. It's either a savings account, or a checking account, or some other type of account. But that doesn't negate the value of having an Account class which SavingsAccount and CheckingAccount can extend: The Account class defines the behavior of adding money into the account.

Defining an abstract class

To define an abstract class, you would include the word abstract in the class definition.

public abstract class Account {
    // definition goes in here
}
Now that I've declared this to be abstract, I've prevented anybody from actually creating a simple account.
Account mine = new Account(); // ERROR: can't instantiate abstract class

Incidentally, classes that are not abstract are typically said to be concrete. These are the classes that you can instantiate.

One classic example of an abstract class is a class for shapes. It doesn't really make much sense to talk about creating a shape object. But nonetheless there are many types of things that are shapes (rectangles, circles, lines) - it would make sense to make them subclasses of the shape.

We could express this by making Shape an abstract class, with (concrete) subclasses for each of the specific types of shapes.

          Shape
        /   |   \
Rectangle Circle Line

In fact, we'll see exactly this sort of structure in a later lab, when we write our own graphics-drawing program.

Defining abstract methods

You can define individual methods within a class as abstract too. When you define a method as abstract, you omit any definition of the method. Instead, anybody who wants to create a subclass must define the method if the subclass is going to be concrete.

For example, it may be we want to force every shape to define a draw() method. We would do it as follows.

public abstract class Shape {
    public abstract void draw(Graphics g);
}
What we've done is to declare that every Shape object must have a draw() method, but we've declined to define one in the Shape class, since there isn't any sensible way of accomplishing this.

When we define the Rectangle subclass, we'll have to define this method in order to be able to actually create a rectangle.

public class Line extends Shape {
    private int x0;
    private int y0;
    private int x1;
    private int y1;

    public Line(int x0, int y0, int x1, int y1) {
        this.x0 = x0;
        this.y0 = y0;
        this.x1 = x1;
        this.y1 = y1;
    }

    public void draw(Graphics g) {
        g.drawLine(x0, y0, x1, y1);
    }
}

Because the draw() method is part of Shape, every instance of Shape must define what to do for the draw() method. Thus, Java can safely allow us to call draw() on what it knows to be a Shape, since it can guarantee that the method has been overridden with an actual implementation.

Shape[] shapes
    = { new Rectangle(0, 0, 3, 4), new Line(30, 30, 50, 50) };
Graphics g = frame.getGraphics();
for(int i = 0; i < shapes.length; i++) shapes[i].draw(g);

Abstract classes and interfaces

Our example with the Shape class is unsatisfying. We could accomplish the same thing using the notion of interface instead. We could define our classes as follows.

public interface Shape {
    public void draw(Graphics g);
}

public class Line implements Shape {
    // (definition goes here)
}
And we could still write the following piece of code.
Shape[] shapes
    = { new Rectangle(0, 0, 3, 4), new Line(30, 30, 50, 50) };
Graphics g = frame.getGraphics();
for(int i = 0; i < shapes.length; i++) shapes[i].draw(g);

So what gives? What's the point of having both of these concepts in Java?

Actually, in the case of our Shape example, we would arguably be better off making it an interface, since it doesn't take advantage of the ability to define instance variables or instance methods. Thus, why would we want to restrict the implementors of Shape to have Shape as the superclass?

But why not include the best of both worlds by permitting a class to extend both classes? This is for obscure technical reasons having to do with how objects actually get put into memory. Some languages (e.g., C++) permit it. It turns out to cause headaches, and the Java designers felt they were best avoided. It doesn't cause a big problem.


Next: None. Up: Abstract classes. Previous: None.