Let's start with a problem. Suppose we're building a program to handle the personel records in an organization. Although this can get complicated in real life, let's simplify our job and assume that we have just three kinds of employees: hourly, exempt (i.e., paid a salary, not by the hour), and managers (exempt employees that supervise other employees). Objects and classes are natural ways to model this information. Let's sketch out what sorts of methods and data (state) that we need for these three kinds of employees:
Exempt Manager Hourly
Now let's think how we could represent this in a Java program. One way would be to have an Employee interface and have three different classes, each of which implement the interface, possibly with some differences in the specific classes.
But if we look a bit more closely at the data and methods involved, we notice that these three classes have much in common. A good rule in software design is "don't repeat yourself", or "do things only once". In this case, we'd like to avoid repeating the same methods and data in each of the different classes and instead, write them once and indicate that they are to be used in several places.
That's the idea behind inheritance. We'd like to capture in the code the idea that these three kinds of employees are really three specialized versions of a general "employee" object. They may have some differences, but they share a fundamental interface and set of data. In the case of our example, we'll declare a class Employee that captures the common behavior and state of the different kinds of employee objects, then extend this class to create the specific kinds of employees. So we get (comments abbreviated to save space)
/** Representation of a generic employee */ public class Employee { // instance variables common to all employees private String name; private int id; /** construct a new employee */ public Employee (String name, int id) { this.name = name; this.id = id; } /** return the name of this employee */ public String getName() { return name; } /** return the pay of this employee */ public double getPay() { ... } ... // other methods omitted to save space
Now, for specific kinds of employees, we extend the original class to create a specific kind of employee
/** Representation of an hourly employee */ public class HourlyEmployee extends Employee { // additional state for hourly employees private double payRate; // hourly pay private double hoursWorked; // hours worked this pay period /** return the pay of this employee */ public double getPay() { double pay; if (hoursWorked <= 40) { pay = payRate * hoursWorked; } else { pay = payRate * (40.0 + 1.5*(hoursWorked-40.0)); } return pay; } ... }
Similarly class ExemptEmployee will extend Employee, and Manager will extend ExemptEmployee (after all, managers are exempt employees with additional responsibilities). There are more details to add (see the code posted online), but we've got enough here that it is time to step back and look a bit more carefully at what we've done. For now, we'll ignore interfaces and just look at classes and inheritance, but we'll come back to that later.
First, what is the relationship between class Employee and the classes ExemptEmployee, HourlyEmployee, and Manager? This is different than what we've seen in simpler objects. If, for example, we were modeling a car, we might have instance variables with information about the car's engine, the tires, and other components. In that case we say that a car "has-a" engine, and it "has-a" set of tires. A car isn't an engine; an engine isn't a car; one is a component of another.
In the case of the employee classes, we say that a class like HourlyEmployee "is-a" Employee. That is, an HourlyEmployee object is an Employee, but possibly with specialized behavior. In particular, an HourlyEmployee object has and can do everything that an Employee object has and can do, but it can do things differently and can have additional state and behavior. Another way we talk about this is to say that HourlyEmployee is a specialization of Employee.
When we write "class HourlyEmployee extends Employee", we say that HourlyEmployee is a subclass of Employee, and Employee is the superclass of HourlyEmployee. The terminology can take a bit of getting used to since a subclass can have a "superset" of the superclass's behavior and state, but the teminology has become standard. The C++ community describes things a bit differently to avoid the confusion: they would say that Employee is the base class and HourlyEmployee is aderived class. Both terminologies are standard and it's worth getting used to both of them.
In Java, there is a single class at the top of the class heirarchy: Object. If a class does not explicitly extend a named class, it is assumed implicitly to extend Object.
A subclass inherits all of the state and behavior from its superclass. That is, even if nothing further is specified, every instance of the subclass contains all of the state of the original class, and all of the methods declared in the original class are available and can be used on instances of the subclass. In addition, a subclass may define additional instance variables (payRate and hoursWorked in the HourlyEmployee example), and it may contain additional methods not declared in the superclass.
While by default a class inherits all of the methods from its superclass, a key part of the power of inheritance is that a subclass can provide its own definition of a method to be used with instances of that particular subclass instead of the one it would otherwise inherit. In this case, the definition of the method in the subclass has to be the same as the one in the superclass, and if this is so, then the new definition of the method in the subclass is said to override the definition in the superclass.
Recall that an instance of a subclass is also considered to be an object that has the type of the superclass. More particularly, an object of, say, type HourlyEmployee also has type Employee. Why and what does this imply? Because of inheritance, we know that every HourlyEmployee object has all of the behavior of an Employee object, although it may implement that inherited behavior differently by overriding some methods. But if we write code that manipulates Employee objects, we can just as easily manipulate HourlyEmployee objects and everything will work. So both of the following code fragments are legal:
Employee e1 = new Employee(...); Employee e2 = new HourlyEmployee(...); double pay = e1.getPay(); double pay = e2.getPay();
Since both objects (e1 and e2) are known to be some sort of Employee object, we know that they have getPay() methods, so the code in the second line will work in either case.
It also works if we write
HourlyEmployee h = new HourlyEmployee(); double pay = h.getPay();
In this case, we're using the more specific subclass. But this won't work:
HourlyEmployee h1 = e1; // e1 declared
The trouble is that the relationship between Employee and HourlyEmployee is not symmetric. While it is true that every HourlyEmployee object "is-a" Employee, it is not true that all Employee objects are HourlyEmployees.
But this doesn't work either
HourlyEmployee h2 = e2; // e2 declared above
We might think it would, but Java's rules for typing require an assignment to be legal based on the declared types of the variables, not on the types of the particular objects that the variables might refer to at the moment. If we happen to know, for instance, that e2 really does refer to an HourlyEmployee object, we can use a cast in the assignment.
HourlyEmployee h3 = (HourlyEmployee)e2; // ok at compile time, check at runtime
The compiler will allow this, but when the assignment is executed at runtime, a check will be made to ensure that variable e2 does, in fact, refer to an HourlyEmployee object. If it doesn't, a ClassCastException will be thrown.
Dynamic Dispatch
So a variable of type Employee can refer to objects of type Employee or any of its subclasses - HourlyEmployee, ExemptEmployee, Manager, and other subclasses that might be defined in the future. But each of these subclasses can override methods defined in the original class. So, if the various classes can override a method like getPay(), which getPay() method gets called if e has type Employee and we execute e.getPay()?
The answer is that the method that gets called depends on the actual type of the object at the time the method call is executed. This is known as dynamic dispatch. The method is picked dynamically depending on the kind of the object that e refers to at the time, and this can change during execution as e is updated to refer to different objects. So when we execute a method call, say o.m(...), the following algorithm is used to figure out which method m should be executed.
This seems straightforward enough, but can sometimes lead to unintuative results. For example, suppose class C contains methods x and y, and method x calls y. It looks like we know the whole story by looking at class C. But it may be that method x will not call the y declared in C if some subclass S of C overrides method y and we're dealing with an instance of class S. The version of y in S would be called instead.
Super
There are at least two reasonably common cases where we want to have code in a subclass interact with similar code in a superclass. The first one is constructors. Normally the superclass will have constructors to initialize instance variables found in the superclass. Since these will often be private, there is no way to initialize them directly from a subclass constructor. Instead we can use the keyword super to specify that a superclass constructor should be executed as the first thing that happens in the subclass constructor.
/** construct a new HourlyEmployee */ public HourlyEmployee(...) { super(...); // execute Employee constructor with matching parameters ... }
The second is that we might want to execute code in a superclass method that's overridden in the subclass. We can't just use the method name directly, since that would result in an infinite recursion. Instead, we can write super.method(parameters) to explicitly use the superclass method. See the getPay() method in the Manager class for an example.
Abstract Classes
Sometimes we want to include a method like getPay() in a superclass like Employee so that it is known to be part of the behavior of every employee, yet there is no sensible definition of getPay that makes sense there - we really need to override this method in each subclass to have an appropriate definition. In that case, we can declare the method itself and the class that contains it to be abstract:
abstract class Employee { ... abstract double getPay(); }
The meaning of this is that the class is incomplete - it specifies at least one method that is not implemented. So we can't create instances of the class directly. Instead, we can extend the class and provide implementations of the abstract methods, then we can create instances of those classes.
This looks a lot like an interface, and it is. What's the difference and where would you use one or the other?
So you use an abstract class when there is some implementation that is used in subclasses, but that you want to write once and inherit, instead of rewritting or copying. If you just want to specify a type, you use an interface - that provides maximum flexibility and does not tie you to any particular implementation details.
If you're writing libaries or working on large systems, it often is good to provide both an interface and an abstract class for important types. Use an interface to specify the type, and an abstract class that provides a partial implementation that can be extended to create specific, complete implementations. There are several good examples of this in the Java collection classes.