This post is intended as a follow-up to this discussion in the dreamincode.net Java forum, and this follow-up. I don’t normally get drawn into these sterile debates, but the thing that ticked me off here was that it is all done under the cover of “educating people.” Being wrong is one thing, being wrong and wrapping falsities under layers of apparent knowledge to impart “wisdom” to others whilst being condescending towards other posters is another one.
The whole point of this post is to discuss the incorrect statement that “interfaces in Java cannot define a IS-A relationship.” And explain why this is incorrect.
I have indeed been told that the following is absurd and nonsensical:
// Payable is an interface, and Employee implements Payable
Payable employee = new Employee();
In normal cases, I would have laughed it off, and moved on (or in the case of an interview, I would have thanked the candidate, and parted ways). But there is a pedagogical dimension to all this, so here I am again.
I would certainly dislike being criticized for not giving references, so at the end of this post I have listed a few; however, I’ll be mostly speaking about my own experience as a Java developer, so yeah, this will be biased big time.
As said before, I truly believe that the statement “using Java interfaces for polymorphism is absurd” is symptomatic of 3 things:
- The misunderstanding of what Java interfaces are all about,
- The scorn the implements keyword is treated with,
- The immoderate idolization some developers devote to inheritance.
Interfaces in Java
The Spec
Let’s look at the Java Language spec has to say about interfaces:
An interface declaration introduces a new reference type whose members are classes, interfaces, constants and abstract methods. This type has no implementation, but otherwise unrelated classes can implement it by providing implementations for its abstract methods. (…) A variable whose declared type is an interface type may have as its value a reference to any instance of a class which implements the specified interface.
This means that (i) an interface is a type (and we could actually stop this post here), and (ii) this is totally valid stuff:
Payable employee = new Employee();
if
Employee implements Payable
.
(This is also valid, mind you:
public interface Payable {
long baseSalary = 10000;
public void pay();
}
public class Employee implements Payable {
public void pay() {
System.out.println("pay Employee " + f);
}
public static void main(String[] args) {
Payable p = new Employee();
p.pay();
}
}
but that’s irrelevant.)
However, the fact it is valid code has never been in doubt, but rather the intent was. Right so, let’s carry on.
Interface as a Type
To quote the Java Tutorial, in the Using Interfaces as Types ([sarcasm]wow, how can these Sun people use such a nonsensical title?[/sarcasm]) section:
When they [“relatable” objects] implement Relatable, they can be of both their own class (or superclass) type and a Relatable type. This gives them some of the advantages of multiple inheritance, where they can have behavior from both a superclass and an interface.
The interesting thing here is the word “behaviour.” The interface brings behaviour to a class; we’ll see what this means in terms of object-oriented programming. We will also see that you can’t just go and remove an interface to prove a heavily-flawed demonstrative point.
instanceof
instanceof
is the type comparison operator in Java. In the case of an interface, this is indeed valid:
new Employee() instanceof Payable
and returns true
.
According to the Java Language spec (See here):
It is a compile-time error if the ReferenceType mentioned after the instanceof operator does not denote a reifiable type (§4.7).
Which does indicate, if there was any doubt about it, that interfaces are reifiable types.
The Serializable case
To finish with this Java interface analysis, let’s look at the Serializable
interface. Wait. It’s empty, what is there to look at? Precisely. What is an empty interface supposed to bring to the table? What is the point of marking classes as Serializable
when there is no method to implement? The point is to give an object the Serializable
type to use polymorphism when invoking the serialization mechanisms. In practice, this is done through the instanceof
keyword, but if the object is not of type Serializable
, the code throws a NotSerializableException
.
Interfaces and OOP
Apparently, it was not just a Java problem anymore. The big guns had to be taken out, and OOP was invoked in the grand pursuit of Java interfaces slurring.
First of all, let’s make something clear: Java interfaces are not a bijection to interfaces in the OOP sense. We have on one hand a programming language feature, and on the other hand an OOP concept. It is not just that the 2 sets mismatch: they are not on the same plane at all.
Interface vs. Implementation
When it comes to design, Design Pattern is the unavoidable reference. The section called Class versus Interface Inheritance discusses at length the importance of programming to an interface.
It’s important to understand the difference between an object’s class and its type.
An object’s class defines how the object is implemented. The class defines the object’s internal state and the implementation of its operations. In contrast, an object’s type only refers to its interface—the set of requests to which it can respond. An object can have many types, and objects of different classes can have the same type.
In this chapter appears the principle that any design-savvy developer knows: “Program to an interface, not an implementation“.
Don’t declare variables to be instances of particular concrete classes. Instead, commit only to an interface defined by an abstract class.
What do they mean by “programming to an interface”? It means that instead of programming around a specific implementation, it is preferable to pass around an interface (in the OOP sense, not necessarily Java interface) and make sure the behaviour is decided at runtime.
The reason is quite straightforward: you don’t want to be tied to a particular implementation. The classic textbook example of this is the Collection
interface. Beginners usually code collections like this:
ArrayList words = new ArrayList();
All the subsequent method calls then would take an ArrayList<String>
as a parameter.
The problem with this approach is that if one day, it is decided to switch to a different implementation (to get a synchronized collection, or to have the words sorted directly with TreeSet
, or to switch to the kick-ass Google Collections library), you’ll have to go through lots of code and do the change. Ideally, you want to avoid this at all costs. But by tying your code to an implementation, you leave the door open to subsequent changes – and developers get annoyed with interface changes (interface as in API, obviously). “WTF? This method was requiring an ArrayList
last week, and now it requires a TreeSet
?!”
(Where I work, changing an interface potentially means that client jars to Web services have to be redeployed, and the 2 or 3 major in-house applications have to be re-released to include the change. That’s usually a messy affair, and always a bit risky, so you usually avoid changing the exposed services)
On the other hand, if you write:
Collection words = new ArrayList();
you can hide the actual implementation, and parse your elements with Iterator
without worrying about potential changes.
The same way you want to program to interface to avoid tight coupling with a specific implementation, you also want to avoid inheriting implementation, as changes to the implementation can have adverse effect on all the subclasses.
Avoid coupling, favour composition over inheritance, program to interfaces, not implementations, that’s the OO principles that any developer would find when opening a decent OOP book. Interestingly enough, this has been discussed at length elsewhere.
Questions?
But if I remove the interface, it is working still. You’ve lost nothing.
My oh my. Again, any candidate stating this in an interview would be out of the door in the next minute.
For this one, we’ll need an example. Let’s say, hey, that Payable one.
public interface Payable {
public void pay();
}
public class Employee implements Payable {
public void pay() {
System.out.println("pay Employee");
}
}
public class Contractor implements Payable {
public void pay() {
System.out.println("pay Contractor");
}
}
public static void main(String[] args) {
Employee e1 = new Employee();
e1.pay();
Payable e2 = new Employee();
e2.pay();
Payable e3 = new Contractor();
e3.pay();
Contractor e4 = new Contractor();
e4.pay();
}
This prints out:
pay Employee
pay Employee
pay Contractor
pay Contractor
Now remove Payable
. Not only they did lose something, but they lost something huge: they lost their type! All these methods relying on the interface would fail:
public void hire(Payable p) {
// ...
}
Contractor
and Employee
also lost their relationship to each other. You just can’t go removing stuff from your design without removing the precious information it was bringing in the first place. That would be just as bad as removing Payable
from below:
public class Payable {
public void pay() {
System.out.println("pay Payable");
}
}
public class Employee extends Payable {
public void pay() {
System.out.println("pay Employee");
}
}
public class Contractor extends Payable {
public void pay() {
System.out.println("pay Contractor");
}
}
Similar to Payable
as an interface, Payable
as a class “doesn’t do anything”, so let’s go and remove it; but again here, you lose a vital thing: the fact that Employee
and Contractor
are Payable
. Everyone can see this is silly, and yet this is exactly the same thing with interfaces.
One could argue that Payable
could have defined other behaviour not overridden by the subclasses, but here it is not the case: it happens in real life too. Usually, you’d make this class abstract
, but it’s not always in your control: Payable
could be a third-party class you’re trying to adapt (as in, the Adapter design pattern) into your own application.
The confirmation to this is the Serializable
example mentioned earlier which must be the epitome of the “useless” interface (it doesn’t define any method): remove it, and it makes a world of difference.
Hey, but Payable is an interface name, it wouldn’t be a class name
This is just a naming convention. Some people suffix their interfaces with -able
, some prefix it with I
(like IComponent
). The problem with this naming convention is that it has contributed to the under-component status interfaces are suffering from. More and more you see this trend being reversed to put the onus back onto the interface (especially with the coming of Spring), and interfaces have the component name, and implementations have suffixed with Impl:
public interface Employee {
public void pay();
}
public class EmployeeImpl implements Employee {
public void pay() {
System.out.println("pay EmployeeImpl");
}
}
This is quite some psychological leap! What this means is that the focus is now on the interface, and that the implementation comes second. Just like it should.
You seem to be confusing abstract classes and interfaces. Are you?
No. I put them in the same bag because they both are useful to define an interface in the OOP sense. From a technical point of view, they are not the same, and they have their own pros and cons. Depending on the situation, you’ll want to favour one or the other, bearing in mind that if you inherit an abstract class, you cannot inherit anything else, but you can provide a default implementation, whereas you can implement lots of different interfaces with their own hierarchy, but face to change all implementing classes if you change anything. Choosing an abstract class over an interface makes also a strong design point (since indeed you cannot inherit anything else); however, this doesn’t mean the type provided by the interfaces is not also important, or should be discarded.
Conclusion
Interfaces have not been defined in the Java language just to “ensure some methods are implemented in the class that implements them”, or as a “CAN-DO” relationship. This view is far too simplistic as we have seen above, and there is no doubt (at least in my mind) that Java interfaces do provide a “IS-A” relationship, and that it is actually their whole raison d‘être. Unfortunately, failing to understand this means you cannot possible leverage technologies such as Spring, mocking, Java proxies, etc. but also shows a fairly questionable knowledge of Java itself, as everything in the language is based around this: serialization, collections, sorting, listeners, EJBs, etc.