Use Interfaces All The Time
Wed Jun 27 09:10:00 EDT 2012
In addition to being useful concepts generally, Java interfaces can be used literally in code as a way of keeping your code as clean and generic as possible. While you can't ever create a new object based on an interface, you CAN use interface names as object types for variables and parameters. For example, this is a legal way to create a Vector
of Object
s:
List<Object> someList = new Vector<Object>();
"That's great and all," you say, "but why bother doing that?" And indeed, in a simple case like a mostly-procedural Java agent, you won't get much benefit from doing that. But imagine a method like this:
public List<String> retrieveValuesFromDatabase();
Since all that method guarantees is that it's going to return some sort of List
, it has a lot of discretion as to the form that list will take. Maybe the programmer starts out with Vector
but then decides that ArrayList
is better for the purpose - they're free to change it without troubling the user of the method one bit. Maybe they want to get a little crazier and make their own custom class that implements List
and provides a live view of a database - again, that can happen with no change to the user's code.
While the primary benefit comes when large libraries use interfaces in their APIs, even moderately-sized structured programs written by one programmer can benefit. Take a (contrived and unsafe) class like this:
public class ExampleClass { private List<String> cache; public ExampleClass() { this.cache = new ArrayList<String>(); } public List<String> getCache() { return this.cache; } public void setCache(List<String> cache) { this.cache = cache; } }
The same benefits mentioned above apply here: because you're referring to your instance variable just as a List
, you're free to change the actual implementing class by changing only one line. The benefits are only compounded when you add more and more-complicated methods to your class.
Additionally, using interfaces when they're not strictly necessary can help avoid bad habits and traps. Our go-to example, Vector
class, pre-dates the Java Collections Framework and was only retrofitted to the List
interface when the framework came out in Java 1.2. It shows its age in a couple ways, not the least of which is the inclusion of a couple methods it uses that are not part of List
, such as elementAt(...)
and addElement(...)
. These are best replaced entirely with get(...)
and add(...)
, which are common to all List
s... and are easier on the eyes, to boot.
It's easy to run into problems with APIs that don't use interfaces extensively, like, say, the Domino API*. Presumably due to the fact that the "new" lotus.domino
classes were implemented in a pre-1.2 JRE, they use Vector
extensively. Most of the time, this is fine - anything expecting a List
will happily accept a Vector
, but the reverse doesn't hold. You don't have to go far to create a problem. Multi-value controls in XPages, when bound to a Java object, prefer the use of ArrayList
(but work great when pointed to an existing List
of any stripe). As a result, this will result in an exception:
<?xml version="1.0" encoding="UTF-8"?> <xp:view xmlns:xp="http://www.ibm.com/xsp/core"> <xp:this.dataContexts> <xp:dataContext var="newRegData" value="${javascript: new java.util.HashMap()}"/> </xp:this.dataContexts> <xp:checkBoxGroup value="#{newRegData.multi}"> <xp:selectItems value="#{javascript:['one', 'two']}"/> </xp:checkBoxGroup> <xp:button id="createDoc" value="Create Doc"> <xp:eventHandler event="onclick" submit="true"><xp:this.action><![CDATA[#{javascript: var doc = database.createDocument() doc.replaceItemValue("MultiValue", newRegData["multi"]) doc.save() }]]></xp:this.action></xp:this.eventHandler> </xp:button> </xp:view>
The problem is that, while ArrayList
is still a List
, replaceItemValue(...)
doesn't give two hoots about interfaces, forcing you to do something like this:
var vec = new java.util.Vector() vec.addAll(newRegData["multi"]) doc.replaceItemValue("MultiValue", vec)
Not pretty, and that's one that could be fixed without changing the outward API.
The upshot of all this is that you can derive a lot of benefit from using interfaces extensively, even when they're not strictly necessary. I even have a habit of writing lines like "List<Object> values = (List<Object>)doc.getItemValue("SomeItem");
", because I am a madman. You may not go as far as I do, but it's still usually a good idea to follow the policy of using interfaces whenever the methods you want are available that way.
* Technically, the the lotus.domino.*
"classes" are all interfaces and the implementing classes vary based on the type of connection you're using (e.g. local vs. CORBA). As usual, Domino provides both good and bad examples simultaneously.