Interoperability
In this section, you'll learn about two aspects of interoperability: compatibility and API design.
Compatibility
The Collections Framework was designed to ensure complete interoperability between the new collection interfaces and the types that have traditionally been used to represent collections: Vector, Hashtable, array, and Enumeration. In this section, you'll learn how to transform traditional collections to new collections and vice versa.
Upward Compatibility
Suppose that you're using an API that returns traditional collections in tandem with another API that requires objects implementing the collection interfaces introduced in Java 2 Platform version 1.2. To make the two APIs interoperate smoothly, you'll have to transform the traditional collections into new collections. Luckily, the Collections Framework makes this easy.
Suppose that the old API returns an array of objects and that the new API requires a Collection. The Collections Framework has a convenience implementation that allows an array of objects to be viewed as a List. You use Arrays.asList to pass an array to any method requiring a Collection or a List:
Foo[] result = oldMethod(arg); newMethod(Arrays.asList(result));
If the old API returns a Vector or a Hashtable, you have no work to do at all, because Vector has been retrofitted to implement the List interface, and Hashtable has been retrofitted to implement Map. Therefore, a Vector may be passed directly to any method calling for a Collection or a List:
Vector result = oldMethod(arg); newMethod(result);
Similarly, a Hashtable may be passed directly to any method calling for a Map:
Hashtable result = oldMethod(arg); newMethod(result);
Less frequently, an API may return an Enumeration that represents a collection of objects. Although there is no direct support for translating an Enumeration into a Collection, it's a simple matter to create a Collection containing all the elements returned by an Enumeration:
Enumeration e = oldMethod(arg); List l = new ArrayList; while (e.hasMoreElements()) { l.add(e.nextElement()); newMethod(l); }
Backward Compatitility
Suppose that you're using an API that returns new collections in tandem with another API that requires you to pass in traditional collections. To make the two APIs interoperate smoothly, you have to transform the new collections into traditional collections. Again, the Collection Framework makes this easy.
Suppose that the new API returns a Collection and that the old API requires an array of Object. As you're probably aware, the Collection interface contains a toArray method, designed expressly for this situation:
Collection c = newMethod(); oldMethod(c.toArray());
What if the old API requires an array of String (or another type) instead of an array of Object? You just use the other form of toArray, the one that takes an array on input:
Collection c = newMethod(); oldMethod((String[]) c.toArray(new String[0]));
If the old API requires a Vector, the standard collection constructor comes in handy:
Collection c = newMethod(); oldMethod(new Vector(c));
The case in which the old API requires a Hashtable is handled analogously:
Map m = newMethod(); oldMethod(new Hashtable(m));
Finally, what do you do if the old API requires an Enumeration? This case isn't common, but it does happen from time to time, and the Collections.enumeration method was provided to handle it. This static factory method takes a Collection and returns an Enumeration over the elements of the Collection:
Collection c = newMethod(); oldMethod(Collections.enumeration(c));
API Design
In this short but important section, you'll learn a few simple guidelines that will allow your API to interoperate seamlessly with all other fine APIs that follow these guidelines. In essence, these rules define what it takes to be a good citizen in the brave new world of collections.
In-Parameters
If your API contains a method that requires a collection on input, it is of paramount importance that you declare the relevant parameter type to be one of the collection interface types. See the section Interfaces (page 470) for more information on interface types. Never use an implementation type, as this defeats the purpose of an interface-based Collections Framework, which is to allow collections to be manipulated without regard to implementation details.
Further, you should always use the least-specific type that makes sense. For example, don't require a List or a Set if a Collection would do. It's not that you should never require a List or a Set on input; it is correct to do so if a method depends on a property of one of these interfaces. For example, many of the algorithms provided by the Java platform require a List on input because they depend on the fact that lists are ordered. As a general rule, however, the best types to use on input are the most general: Collection and Map.
Caution
Never, ever define your own ad hoc collection class and require objects of this class on input. By doing this, you'd lose all the benefits provided by the Collection Framework.
Return Values
You can afford to be much more flexible with return values than with input parameters. It's fine to return an object of any type that implements or that extends one of the collection interfaces. This can be one of the interfaces or a special-purpose type that extends or implements one of these interfaces.
For example, one could imagine an image-processing package that returned objects of a new class that implements List, called ImageList. In addition to the List operations, ImageList could support any application-specific operations that seemed desirable. For example, it might provide an indexImage operation that returned an image containing thumbnail images of each graphic in the ImageList. It's critical to note that even if the API furnishes ImageList objects on output, it should accept arbitrary Collection (or perhaps List) objects on input.
In one sense, return values should have the opposite behavior of input parameters: It's best to return the most specific applicable collection interface rather than the most general. For example, if you're sure that you'll always return a SortedMap, you should give the relevant method the return type of SortedMap rather than Map. SortedMap objects are both more time consuming to build than ordinary Map objects are and also more powerful. Given that your module has already invested the time to build a SortedMap, it makes good sense to give the user access to its increased power. Furthermore, the user will be able to pass the returned object to methods that demand a SortedMap, as well as those that accept any Map.