Chapter 8. Interfaces and Abstract Classes: Serious Polymorphism

image with no caption

Inheritance is just the beginning. To exploit polymorphism, we need interfaces (and not the GUI kind). We need to go beyond simple inheritance to a level of flexibility and extensibility you can get only by designing and coding to interface specifications. Some of the coolest parts of Java wouldn’t even be possible without interfaces, so even if you don’t design with them yourself, you still have to use them. But you’ll want to design with them. You’ll need to design with them. You’ll wonder how you ever lived without them. What’s an interface? It’s a 100% abstract class. What’s an abstract class? It’s a class that can’t be instantiated. What’s that good for? You’ll see in just a few moments. But if you think about the end of the last chapter, and how we used polymorphic arguments so that a single Vet method could take Animal subclasses of all types, well, that was just scratching the surface. Interfaces are the poly in polymorphism. The ab in abstract. The caffeine in Java.

The class structure isn’t too bad. We’ve designed it so that duplicate code is kept to a minimum, and we’ve overridden the methods that we think should have subclass-specific implementations. We’ve made it nice and flexible from a polymorphic perspective, because we can design Animal-using programs with Animal arguments (and array declarations), so that any Animal subtype—including those we never imagined at the time we wrote our code—can be passed in and used at runtime. We’ve put the common protocol for all Animals (the four methods that we want the world to know all Animals have) in the Animal superclass, and we’re ready to start making new Lions and Tigers and Hippos.

image with no caption

We know we can say:

Wolf aWolf = new Wolf();
image with no caption

And we know we can say:

Animal aHippo = new Hippo();
image with no caption

But here’s where it gets weird:

Animal anim = new Animal();
image with no caption

What does a new Animal() object look like?

image with no caption

What are the instance variable values?

Some classes just should not be instantiated!

It makes sense to create a Wolf object or a Hippo object or a Tiger object, but what exactly is an Animal object? What shape is it? What color, size, number of legs...

Trying to create an object of type Animal is like a nightmare Star Trek™ transporter accident. The one where somewhere in the beam-me--up process something bad happened to the buffer.

But how do we deal with this? We need an Animal class, for inheritance and polymorphism. But we want programmers to instantiate only the less abstract subclasses of class Animal, not Animal itself. We want Tiger objects and Lion objects, not Animal objects.

Fortunately, there’s a simple way to prevent a class from ever being instantiated. In other words, to stop anyone from saying “new” on that type. By marking the class as abstract, the compiler will stop any code, anywhere, from ever creating an instance of that type.

You can still use that abstract type as a reference type. In fact,that’s a big part of why you have that abstract class in the first place (to use it as a polymorphic argument or return type, or to make a polymorphic array).

When you’re designing your class inheritance structure, you have to decide which classes are abstract and which are concrete. Concrete classes are those that are specific enough to be instantiated. A concrete class just means that it’s OK to make objects of that type.

Making a class abstract is easy—put the keyword abstract before the class declaration:

abstract class Canine extends Animal {
   public void roam() { }
}

Besides classes, you can mark methods abstract, too. An abstract class means the class must be extended; an abstract method means the method must be overridden. You might decide that some (or all) behaviors in an abstract class don’t make any sense unless they’re implemented by a more specific subclass. In other words, you can’t think of any generic method implementation that could possibly be useful for subclasses. What would a generic eat() method look like?

An abstract method has no body!

Because you’ve already decided there isn’t any code that would make sense in the abstract method, you won’t put in a method body. So no curly braces—just end the declaration with a semicolon.

image with no caption
image with no caption

If you declare an abstract method, you MUST mark the class abstract as well. You can’t have an abstract method in a non-abstract class.

If you put even a single abstract method in a class, you have to make the class abstract. But you can mix both abstract and non-abstract methods in the abstract class.

image with no caption

Implementing an abstract method is just like overriding a method.

Abstract methods don’t have a body; they exist solely for polymorphism. That means the first concrete class in the inheritance tree must implement all abstract methods.

You can, however, pass the buck by being abstract yourself. If both Animal and Canine are abstract, for example, and both have abstract methods, class Canine does not have to implement the abstract methods from Animal. But as soon as we get to the first concrete subclass, like Dog, that subclass must implement all of the abstract methods from both Animal and Canine.

But remember that an abstract class can have both abstract and non-abstract methods, so Canine, for example, could implement an abstract method from Animal, so that Dog didn’t have to. But if Canine says nothing about the abstract methods from Animal, Dog has to implement all of Animal’s abstract methods.

When we say “you must implement the abstract method”, that means you must provide a body. That means you must create a non-abstract method in your class with the same method signature (name and arguments) and a return type that is compatible with the declared return type of the abstract method. What you put in that method is up to you. All Java cares about is that the method is there, in your concrete subclass.

You know where this is heading. We want to change the type of the array, along with the add() method argument, to something above Animal. Something even more generic, more abstract than Animal. But how can we do it? We don’t have a superclass for Animal.

Then again, maybe we do...

Remember those methods of ArrayList? Look how the remove, contains, and indexOf method all use an object of type... Object!

Every class in Java extends class Object.

Class Object is the mother of all classes; it’s the superclass of everything.

Even if you take advantage of polymorphism, you still have to create a class with methods that take and return your polymorphic type. Without a common superclass for everything in Java, there’d be no way for the developers of Java to create classes with methods that could take your custom types... types they never knew about when they wrote the ArrayList class.

So you were making subclasses of class Object from the very beginning and you didn’t even know it. Every class you write extends Object, without your ever having to say it. But you can think of it as though a class you write looks like this:

public class Dog extends Object { }

But wait a minute, Dog already extends something, Canine. That’s OK. The compiler will make Canine extend Object instead. Except Canine extends Animal. No problem, then the compiler will just make Animal extend Object.

Any class that doesn’t explicitly extend another class, implicitly extends Object.

So, since Dog extends Canine, it doesn’t directly extend Object (although it does extend it indirectly), and the same is true for Canine, but Animal does directly extend Object.

If you were Java, what behavior would you want every object to have? Hmmmm... let’s see... how about a method that lets you find out if one object is equal to another object? What about a method that can tell you the actual class type of that object? Maybe a method that gives you a hashcode for the object, so you can use the object in hashtables (we’ll talk about Java’s hashtables in Chapter 16). Oh, here’s a good one—a method that prints out a String message for that object.

And what do you know? As if by magic, class Object does indeed have methods for those four things. That’s not all, though, but these are the ones we really care about.

image with no caption

An object contains everything it inherits from each of its superclasses. That means every object—regardless of its actual class type—is also an instance of class Object.That means any object in Java can be treated not just as a Dog, Button, or Snowboard, but also as an Object. When you say new Snowboard(), you get a single object on the heap—a Snowboard object—but that Snowboard wraps itself around an inner core representing the Object (capital “O”) portion of itself.

image with no caption

When you put an object in an ArrayList<Object>, you can treat it only as an Object, regardless of the type it was when you put it in.

When you get a reference from an ArrayList<Object>, the reference is always of type Object.

That means you get an Object remote control.

‘Polymorphism’ means ‘many forms’.

You can treat a Snowboard as a Snowboard or as an Object.

If a reference is like a remote control, the remote control takes on more and more buttons as you move down the inheritance tree. A remote control (reference) of type Object has only a few buttons—the buttons for the exposed methods of class Object. But a remote control of type Snowboard includes all the buttons from class Object, plus any new buttons (for new methods) of class Snowboard. The more specific the class, the more buttons it may have.

Of course that’s not always true; a subclass might not add any new methods, but simply override the methods of its superclass. The key point is that even if the object is of type Snowboard, an Object reference to the Snowboard object can’t see the Snowboard-specific methods.

image with no caption
image with no caption

Casting an object reference back to its real type.

image with no caption

It’s really still a Dog object, but if you want to call Dog-specific methods, you need a reference declared as type Dog. If you’re sure* the object is really a Dog, you can make a new Dog reference to it by copying the Object reference, and forcing that copy to go into a Dog reference variable, using a cast (Dog). You can use the new Dog reference to call Dog methods.

image with no caption

*If you’re not sure it’s a Dog, you can use the instanceof operator to check. Because if you’re wrong when you do the cast, you’ll get a ClassCastException at runtime and come to a grinding halt.

if (o instanceof Dog) {
   Dog d = (Dog) o;
}

So now you’ve seen how much Java cares about the methods in the class of the reference variable.

You can call a method on an object only if the class of the reference variable has that method.

Think of the public methods in your class as your contract, your promise to the outside world about the things you can do.

image with no caption

When you write a class, you almost always expose some of the methods to code outside the class. To expose a method means you make a method accessible, usually by marking it public.

Imagine this scenario: you’re writing code for a small business accounting program. A custom application for “Simon’s Surf Shop”. The good reuser that you are, you found an Account class that appears to meet your needs perfectly, according to its documentation, anyway. Each account instance represents an individual customer’s account with the store. So there you are minding your own business invoking the credit() and debit() methods on an account object when you realize you need to get a balance on an account. No problem—there’s a getBalance() method that should do nicely.

image with no caption

Except... when you invoke the getBalance() method, the whole thing blows up at runtime. Forget the documentation, the class does not have that method. Yikes!

But that won’t happen to you, because everytime you use the dot operator on a reference (a.doStuff()), the compiler looks at the reference type (the type ‘a’ was declared to be) and checks that class to guarantee the class has the method, and that the method does indeed take the argument you’re passing and return the kind of value you’re expecting to get back.

Just remember that the compiler checks the class of the reference variable, not the class of the actual object at the other end of the reference.

OK, pretend you’re a Dog. Your Dog class isn’t the only contract that defines who you are. Remember, you inherit accessible (which usually means public) methods from all of your superclasses.

True, your Dog class defines a contract.

But not all of your contract.

Everything in class Canine is part of your contract.

Everything in class Animal is part of your contract.

Everything in class Object is part of your contract.

According to the IS-A test, you are each of those things—Canine, Animal, and Object.

But what if the person who designed your class had in mind the Animal simulation program, and now he wants to use you (class Dog) for a Science Fair Tutorial on Animal objects.

That’s OK, you’re probably reusable for that.

But what if later he wants to use you for a PetShop program? You don’t have any Pet behaviors. A Pet needs methods like beFriendly() and play().

OK, now pretend you’re the Dog class programmer. No problem, right? Just add some more methods to the Dog class. You won’t be breaking anyone else’s code by adding methods, since you aren’t touching the existing methods that someone else’s code might be calling on Dog objects.

Can you see any drawbacks to that approach (adding Pet methods to the Dog class)?

On the next few pages, we’re going to walk through some possibilities. We’re not yet worried about whether Java can actually do what we come up with. We’ll cross that bridge once we have a good idea of some of the tradeoffs.

Note

  1. Option one

    We take the easy path, and put pet methods in class Animal.

    Pros:

    image with no caption

    All the Animals will instantly inherit the pet behaviors. We won’t have to touch the existing Animal subclasses at all, and any Animal subclasses created in the future will also get to take advantage of inheriting those methods. That way, class Animal can be used as the polymorphic type in any program that wants to treat the Animals as pets

    Cons:

    So... when was the last time you saw a Hippo at a pet shop? Lion? Wolf? Could be dangerous to give non-pets pet methods.

    Also, we almost certainly WILL have to touch the pet classes like Dog and Cat, because (in our house, anyway) Dogs and Cats tend to implement pet behaviors VERY differently.

  2. Option two

    We start with Option One, putting the pet methods in class Animal, but we make the methods abstract, forcing the Animal subclasses to override them.

    Pros:

    That would give us all the benefits of Option One, but without the drawback of having non-pet Animals running around with pet methods (like beFriendly()). All Animal classes would have the method (because it’s in class Animal), but because it’s abstract the non-pet Animal classes won’t inherit any functionality. All classes MUST override the methods, but they can make the methods “do-nothings”.

    image with no caption

    Cons:

    Because the pet methods in the Animal class are all abstract, the concrete Animal subclasses are forced to implement all of them. (Remember, all abstract methods MUST be implemented by the first concrete subclass down the inheritance tree.) What a waste of time! You have to sit there and type in each and every pet method into each and every concrete nonpet class, and all future subclasses as well. And while this does solve the problem of non-pets actually DOING pet things (as they would if they inherited pet functionality from class Animal), the contract is bad. Every non-pet class would be announcing to the world that it, too, has those pet methods, even though the methods wouldn’t actually DO anything when called.

    This approach doesn’t look good at all. It just seems wrong to stuff everything into class Animal that more than one Animal type might need, UNLESS it applies to ALL Animal subclasses.

  3. Option three

    Put the pet methods ONLY in the classes where they belong.

    Pros:

    No more worries about Hippos greeting you at the door or licking your face. The methods are where they belong, and ONLY where they belong. Dogs can implement the methods and Cats can implement the methods, but nobody else has to know about them.

    Cons:

    Two Big Problems with this approach. First off, you’d have to agree to a protocol, and all programmers of pet Animal classes now and in the future would have to KNOW about the protocol. By protocol, we mean the exact methods that we’ve decided all pets should have. The pet contract without anything to back it up. But what if one of the programmers gets it just a tiny bit wrong? Like, a method takes a String when it was supposed to take an int? Or they named it doFriendly() instead of beFriendly()? Since it isn’t in a contract, the compiler has no way to check you to see if you’ve implemented the methods correctly. Someone could easily come along to use the pet Animal classes and find that not all of them work quite right.

    image with no caption

    And second, you don’t get to use polymorphism for the pet methods. Every class that needs to use pet behaviors would have to know about each and every class! In other words, you can’t use Animal as the polymorphic type now, because the compiler won’t let you call a Pet method on an Animal reference (even if it’s really a Dog object) because class Animal doesn’t have the method.

So what we REALLY need is:

A way to have pet behavior in just the pet classes

A way to guarantee that all pet classes have all of the same methods defined (same name, same arguments, same return types, no missing methods, etc.), without having to cross your fingers and hope all the programmers get it right.

A way to take advantage of polymorphism so that all pets can have their pet methods called, without having to use arguments, return types, and arrays for each and every pet class.

It looks like we need TWO superclasses at the top

image with no caption

There’s just one problem with the “two superclasses” approach...

It’s called “multiple inheritance” and it can be a Really Bad Thing.

That is, if it were possible to do in Java.

But it isn’t, because multiple inheritance has a problem known as The Deadly Diamond of Death.

Deadly Diamond of Death

image with no caption

A language that allows the Deadly Diamond of Death can lead to some ugly complexities, because you have to have special rules to deal with the potential ambiguities. And extra rules means extra work for you both in learning those rules and watching out for those “special cases”. Java is supposed to be simple, with consistent rules that don’t blow up under some scenarios. So Java (unlike C++) protects you from having to think about the Deadly Diamond of Death. But that brings us back to the original problem! How do we handle the Animal/Pet thing?

Java gives you a solution. An interface. Not a GUI interface, not the generic use of the word interface as in, “That’s the public interface for the Button class API,” but the Java keyword interface.

A Java interface solves your multiple inheritance problem by giving you much of the polymorphic benefits of multiple inheritance without the pain and suffering from the Deadly Diamond of Death (DDD).

The way in which interfaces side-step the DDD is surprisingly simple: make all the methods abstract! That way, the subclass must implement the methods (remember, abstract methods must be implemented by the first concrete subclass), so at runtime the JVM isn’t confused about which of the two inherited versions it’s supposed to call.

image with no caption

To DEFINE an interface:

image with no caption

To IMPLEMENT an interface:

image with no caption
image with no caption

Classes from different inheritance trees can implement the same interface.

image with no caption

When you use a class as a polymorphic type (like an array of type Animal or a method that takes a Canine argument), the objects you can stick in that type must be from the same inheritance tree. But not just anywhere in the inheritance tree; the objects must be from a class that is a subclass of the polymorphic type. An argument of type Canine can accept a Wolf and a Dog, but not a Cat or a Hippo.

But when you use an interface as a polymorphic type (like an array of Pets), the objects can be from anywhere in the inheritance tree. The only requirement is that the objects are from a class that implements the interface. Allowing classes in different inheritance trees to implement a common interface is crucial in the Java API. Do you want an object to be able to save its state to a file? Implement the Serializable interface. Do you need objects to run their methods in a separate thread of execution? Implement Runnable. You get the idea. You’ll learn more about Serializable and Runnable in later chapters, but for now, remember that classes from any place in the inheritance tree might need to implement those interfaces. Nearly any class might want to be saveable or runnable.

Better still, a class can implement multiple interfaces!

A Dog object IS-A Canine, and IS-A Animal, and IS-A Object, all through inheritance. But a Dog IS-A Pet through interface implementation, and the Dog might implement other interfaces as well. You could say:

public class Dog extends Animal implements
Pet, Saveable, Paintable { ... }

How do you know whether to make a class, a subclass, an abstract class, or an interface?



[8] There is an exception to this—an abstract class can have static members (see Chapter 10).