The Java Collections Framework was designed to ensure complete interoperability between the core collection interfaces and the types that were used to represent collections in the early versions of the Java platform:
Hashtable, array, and
Enumeration. In this section, you'll learn how to transform old collections to the Java Collections Framework collections and vice versa.
Suppose that you're using an API that returns legacy collections in tandem with another API that requires objects implementing the collection interfaces. To make the two APIs interoperate smoothly, you'll have to transform the legacy collections into modern collections. Luckily, the Java Collections Framework makes this easy.
Suppose the old API returns an array of objects and 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
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 was retrofitted to implement the
List interface, and
Hashtable was retrofitted to implement
Map. Therefore, a
Vector may be passed directly to any method calling for a
Collection or a
Vector result = oldMethod(arg); newMethod(result);
Hashtable may be passed directly to any method calling for a
Hashtable result = oldMethod(arg); newMethod(result);
Less frequently, an API may return an
Enumeration that represents a collection of objects. The
Collections.list method translates an
Enumeration into a
Enumeration e = oldMethod(arg); newMethod(Collections.list(e));
Suppose you're using an API that returns modern collections in tandem with another API that requires you to pass in legacy collections. To make the two APIs interoperate smoothly, you have to transform modern collections into old collections. Again, the Java Collections Framework makes this easy.
Suppose the new API returns a
Collection, and 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));
If the old API requires a
Vector, the standard collection constructor comes in handy.
Collection c = newMethod(); oldMethod(new Vector(c));
The case where 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 is a static factory method that takes a
Collection and returns an
Enumeration over the elements of the
Collection c = newMethod(); oldMethod(Collections.enumeration(c));
In this short but important section, you'll learn a few simple guidelines that will allow your API to interoperate seamlessly with all other APIs that follow these guidelines. In essence, these rules define what it takes to be a good "citizen" in the world of collections.
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. Never use an implementation type because 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:
Caution: Never define your own ad hoc
collectionclass and require objects of this class on input. By doing this, you'd lose all the benefits provided by the Java Collections Framework.
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 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, called
ImageList, that returned objects of a new class that implements
List. In addition to the
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 instances on output, it should accept arbitrary
Collection (or perhaps
List) instances 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
SortedMap instances are more time-consuming to build than ordinary
Mapinstances and are 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
There are currently plenty of APIs out there that define their own ad hoc collection types. While this is unfortunate, it's a fact of life, given that there was no Collections Framework in the first two major releases of the Java platform. Suppose you own one of these APIs; here's what you can do about it.
If possible, retrofit your legacy collection type to implement one of the standard collection interfaces. Then all the collections you return will interoperate smoothly with other collection-based APIs. If this is impossible (for example, because one or more of the preexisting type signatures conflict with the standard collection interfaces), define an adapter class that wraps one of your legacy collections objects, allowing it to function as a standard collection. (The
Adapter class is an example of a custom implementation.)
Retrofit your API with new calls that follow the input guidelines to accept objects of a standard collection interface, if possible. Such calls can coexist with the calls that take the legacy collection type. If this is impossible, provide a constructor or static factory for your legacy type that takes an object of one of the standard interfaces and returns a legacy collection containing the same elements (or mappings). Either of these approaches will allow users to pass arbitrary collections into your API.