Classes aren't always created from scratch. Java allows for you to make specializations of existing classes. In object-oriented parlance, this is referred to as inheritance—one of the pillars of object-oriented programming (along with encapsulation, which you have already studied). Inheritance attempts to mimic real-world relationships, such as the different types of categories of “vehicles” in the diagram below:
Declaration
In Java you can specialize an existing class to create a child or subclass of the existing class. This is done with the extends keyword.
Inheritance
What is the benefit of this? One of the main motivations is to consolidate common code that would otherwise be repeated. Code that apply to all the classes can be placed in the super class of commonality rather than being duplicated in all the subclasses.
Now every instance of Vehicle, including Car, will know how to move.
Polymorphism
Along with encapsulation and inheritance, the third central feature of object-oriented programming is polymorphism (which comes from Greek and means “many shapes”). In object-oriented terms, this means that a subclass can change the way a method functions by overriding the version that appears in the base class.
There is an important thing to note here: subclasses can change their behavior by overriding classes, and this holds even if the type of the reference is of the super class! Not only can you assign an Airplane to a Vehicle reference variable, calling its move() method will still invoke the Airplane version instead of the Vehicle version.
Liskov Substitution Principle
Polymorphism is a powerful concept. It means that a method can take the general, base type as a parameter and know that the correct behavior will occur regardless of which subclass instance is passed. Consider a Raceway class that knows how to race vehicles.
We can now use the Raceway to race a car against an airplane. The Raceway does not care what type of vehicle is given; it simply calls the move() method of each one, and the Vehicle instance knows how to move based upon its own implementation of move() (or the one it inherited from Vehicle if it did not override it).
One implication of polymorphism is that, because a caller does not know which subclass method implementation is being invoked, subclasses should design their implementations so that their behavior matches the contract of the base method. The Liskov Substitution Principle says in part that if some logic in the program expects an instance of the base class (such as Vehicle), you should be able to substitute an instance of any subclass (such as Car or Airplane) and the program should continue to function. To make this happen, all the subclasses overrides must continue to match the original method contract, which we discussed in the lesson on contract programming. This means that:
the contract must be written sufficiently general to allow for variations in implementation, while concentrating on the central semantics of the behavior; and
the subclass implementations must be written carefully so that they adhere to the contract of the base method.
Super Methods
Sometimes a subclass doesn't want to replace functionality altogether—it may want merely to add to the existing functionality of a super class. A subclass is therefore allows to invoke the super class version of a function before, during, or after the specialized version. Take for example a “tiller truck”, a type of fire engine that has separate drivers for the front and rear sets of wheels.
Steering is performed independently, so a TillerTruck may want to add to the default steer() function. The super class version of steer is invoked using super.steer().
Multiple Inheritance
Let's say that the class Car has a method turnQuickly(), while the class Truck has a method haulStuff(). You decide that a Pickup is sort of part Car, part Truck, and you wish it could inherit aspects of both classes.
This is called multiple inheritance, and some programming languages allow it. Java only allows single inheritance, allowing a class to extend a single class, not multiple classes.
How might you then include common features of Car and Truck in your Pickup class, if you can't use inheritance? You might extract out the common functionality into a separate class, so that Pickup contains an instance of this class; this is called composition, because Pickup is “composed” of this functionality rather than inheriting it.
Abstract Classes
As the inheritance hierarchy for Vehicle grows, the number of specialized classes increases. The Vehicle class at the root of the hierarchy may become so general it serves merely as a base commonality of all specialized vehicles. There would be little use in instantiating such a general Vehicle class instead of a more specific subclass such as Car or Airplane.
Java allows you to declare that a class is “abstract” and therefore must not be instantiated. An abstract class serves as sort of a “placeholder” in the inheritance hierarchy, an abstraction that encapsulates information and functionality to be used by specialized classes. You can make a class abstract by using the abstract keyword in the class declaration.
Construction
Subclasses present special difficulties when it comes to construction and initialization. Sometimes the class being extended has a constructor that initializes the super class state. The constructor of the subclass must invoke some constructor of the super class, and this is done with the same super keyword which is used to access super class method versions, except that in this case super(…) is used as if it were a method.
The reason you must call some constructor, either explicitly or implicitly, of the super class is that Java must construct and initialize each base class before constructing the subclass. Until the super class has been fully constructed, the subclass is in a limbo state and its variables are not available.
instanceof
To find out whether some reference is to an instance of a particular class, you use the Java instanceof operator. When wouldn't the instance type be clear? Consider the concept of polymorphism, in which you are provided with a reference of a base class without knowing which base class. Together with type casting (which you learned about in an early lesson about types), you can access functionality that may only be available through one of the subclasses, such as AbstractWheeledVehicle.getWheelCount() above.
Review
Gotchas
Inheritance has several shortcomings:
Like real-world categories, it is easy to separate classes based upon differing and/or overlapping criteria, such as number of wheels and utility for Vehicle types; this leads to inflexibility for future specialization.
Java only allows single inheritance.
If you override a method and want to add functionality instead of replace functionality, don't forget to call the super version of the method.
In the Real World
Inheritance is being less favored nowadays, because isn't as flexible as other techniques such as composition and interfaces for sharing commonalities.
Avoid calling instance methods during the constructor if a child class might override those methods.
Make instance variables private unless there is an exceptional need to do otherwise, and force all access to the values, even from subclasses, to go through accessor methods.
Think About It
Don't blindly use inheritance because you think that inheritance hierarchy exists in the real world. Think of the most elegant way to establish relationships to bring about some functionality. Inheritance is only one tool; composition or interfaces may many times be a better approach.
Self Evaluation
What are the three central features that make a programming language object-oriented?
How do you make a subclass of Object in Java?
How could you prevent someone from extending your class?
How could you prevent someone from extending your method?
How would you write Javadocs for a method that @Overrides another, if you only want to explain how the overriding method differs from the other?
What is an abstract class? What are they useful for?
What is the toString() method? How do you get one? How is it used? Is the resulting value a good thing to place in a user interface? Why or why not?
What is multiple inheritance, and how does it relate to Java?
What does the phrase “prefer composition over inheritance” mean? Can you think of an example to illustrate this idea?
Task
Your company's marketing manager comes to you and says that users now want to know, not just the top best-selling books of all times, but also the current top largest circulation magazines in the United States, as reported by Wikipedia on 2015-09-08.
Perhaps you think for a moment and decide that, in the real world, books and magazines are both types of publications . Both books and magazines share some things in common, such as a “name” or “title”; the number in print, and date of first publication. Create a base class to represent a “publication”, and have both the “book” and “magazine” classes extend the “publication” class.
Create appropriate unit tests for the new “magazine” class.
You decide that there is no point for anyone to create a bare “publication” instance (that isn't a “book” or a “magazine”, in other words). Find some way to prevent the “publication” class from being instantiated by itself.
Rather than keep separate arrays, you want to just mix the books and magazines together. Replace the array of books with an array of publications.
You now have to fulfill the new requirements. Create two methods, one to print out books and another to print out magazines.
For each printing method, allow it to accept varargs of the “publication” type.
The book printing method will ignore non-books.
The magazine printing method will ignore non-magazines.
To make your life easier as a developer for logging, override toString() for both the “book” and “magazine” classes to provide useful information to a developer.
As a debugging aid, after printing out the books and magazines separately, bulk-print all the publications using their toString() methods.