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.
We know we can say:
Wolf aWolf = new Wolf();
And we know we can say:
Animal aHippo = new Hippo();
But here’s where it gets weird:
Animal anim = new Animal();
What does a new Animal() object look like?
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() { } }
An abstract class means that nobody can ever make a new instance of that class. You can still use that abstract class as a declared reference type, for the purpose of polymorphism, but you don’t have to worry about somebody making objects of that type. The compiler guarantees it.
An abstract class has virtually[8] no use, no value, no purpose in life, unless it is extended.
With an abstract class, the guys doing the work at runtime are instances of a subclass of your abstract class.
A class that’s not abstract is called a concrete class. In the Animal inheritance tree, if we make Animal, Canine, and Feline abstract, that leaves Hippo, Wolf, Dog, Tiger, Lion, and Cat as the concrete subclasses.
Flip through the Java API and you’ll find a lot of abstract classes, especially in the GUI library. What does a GUI Component look like? The Component class is the superclass of GUI-related classes for things like buttons, text areas, scrollbars, dialog boxes, you name it. You don’t make an instance of a generic Component and put it on the screen, you make a JButton. In other words, you instantiate only a concrete subclass of Component, but never Component itself.
abstract or concrete?
How do you know when a class should be abstract? Wine is probably abstract. But what about Red and White? Again probably abstract (for some of us, anyway). But at what point in the hierarchy do things become concrete?
Do you make PinotNoir concrete, or is it abstract too? It looks like the Camelot Vineyards 1997 Pinot Noir is probably concrete no matter what. But how do you know for sure?
Look at the Animal inheritance tree above. Do the choices we’ve made for which classes are abstract and which are concrete seem appropriate? Would you change anything about the Animal inheritance tree (other than adding more Animals, of course)?
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.
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.
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.
Let’s say that we want to write our own kind of list class, one that will hold Dog objects, but pretend for a moment that we don’t know about the ArrayList class. For the first pass, we’ll give it just an add() method. We’ll use a simple Dog array (Dog []) to keep the added Dog objects, and give it a length of 5. When we reach the limit of 5 Dog objects, you can still call the add() method but it won’t do anything. If we’re not at the limit, the add() method puts the Dog in the array at the next available index position, then increments that next available index (nextIndex).
Building our own Dog-specific list
(Perhaps the world’s worst attempt at making our own ArrayList kind of class, from scratch.)
We have a few options here:
1) Make a separate class, MyCatList, to hold Cat objects. Pretty clunky.
2) Make a single class, DogAndCatList, that keeps two different arrays as instance variables and has two different add() methods: addCat(Cat c) and addDog(Dog d). Another clunky solution.
3) Make heterogeneous AnimalList class, that takes any kind of Animal subclass (since we know that if the spec changed to add Cats, sooner or later we’ll have some other kind of animal added as well). We like this option best, so let’s change our class to make it more generic, to take Animals instead of just Dogs. We’ve highlighted the key changes (the logic is the same, of course, but the type has changed from Dog to Animal everywhere in the code.
Building our own Animal-specific list
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!
Many of the ArrayList methods use the ultimate polymorphic type, Object. Since every class in Java is a subclass of Object, these ArrayList methods can take anything!
(Note: as of Java 5.0, the get() and add() methods actually look a little different than the ones shown here, but for now this is the way to think about it. We’ll get into the full story a little later.)
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.
Before you run off and start using type Object for all your ultra-flexible argument and return types, you need to consider a little issue of using type Object as a reference. And keep in mind that we’re not talking about making instances of type Object; we’re talking about making instances of some other type, but using a reference of type Object.
When you put an object into an ArrayList<Dog>, it goes in as a Dog, and comes out as a Dog:
But what happens when you declare it as ArrayList<Object>? If you want to make an ArrayList that will literally take any kind of Object, you declare it like this:
But what happens when you try to get the Dog object and assign it to a Dog reference?
Everything comes out of an ArrayList<Object> as a reference of type Object, regardless of what the actual object is, or what the reference type was when you added the object to the list.
The problem with having everything treated polymorphically as an Object is that the objects appear to lose (but not permanently) their true essence. The Dog appears to lose its dogness. Let’s see what happens when we pass a Dog to a method that returns a reference to the same Dog object, but declares the return type as type Object rather than Dog.
So now we know that when an object is referenced by a variable declared as type Object, it can’t be assigned to a variable declared with the actual object’s type. And we know that this can happen when a return type or argument is declared as type Object, as would be the case, for example, when the object is put into an ArrayList of type Object using ArrayList<Object>. But what are the implications of this? Is it a problem to have to use an Object reference variable to refer to a Dog object? Let’s try to call Dog methods on our Dog-That-Compiler-Thinks-Is-An-Object:
The compiler decides whether you can call a method based on the reference type, not the actual object type.
Even if you know the object is capable (“...but it really is a Dog, honest...”), the compiler sees it only as a generic Object. For all the compiler knows, you put a Button object out there. Or a Microwave object. Or some other thing that really doesn’t know how to bark.
The compiler checks the class of the reference type—not the object type—to see if you can call a method using that reference.
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.
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.
Casting an object reference back to its real type.
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.
*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.
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.
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)?
Think about what YOU would do if YOU were the Dog class programmer and needed to modify the Dog so that it could do Pet things, too. We know that simply adding new Pet behaviors (methods) to the Dog class will work, and won’t break anyone else’s code.
But... this is a PetShop program. It has more than just Dogs! And what if someone wants to use your Dog class for a program that has wild Dogs? What do you think your options might be, and without worrying about how Java handles things, just try to imagine how you’d like to solve the problem of modifying some of your Animal classes to include Pet behaviors.
Stop right now and think about it, before you look at the next page where we begin to reveal everything.
(thus rendering the whole exercise completely useless, robbing you of your One Big Chance to burn some brain calories)
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.
Option one
We take the easy path, and put pet methods in class Animal.
Pros:
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.
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”.
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.
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.
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:
It looks like we need TWO superclasses at the top
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
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.
To DEFINE an interface:
To IMPLEMENT an interface:
Classes from different inheritance trees can implement the same interface.
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?
Make a class that doesn’t extend anything (other than Object) when your new class doesn’t pass the IS-A test for any other type.
Make a subclass (in other words, extend a class) only when you need to make a more specific version of a class and need to override or add new behaviors.
Use an abstract class when you want to define a template for a group of subclasses, and you have at least some implementation code that all subclasses could use. Make the class abstract when you want to guarantee that nobody can make objects of that type.
Use an interface when you want to define a role that other classes can play, regardless of where those classes are in the inheritance tree.