Checked Collections


Suppose you're working with legacy code. In fact, most systems you encounter will include code written for older versions of Java. This is the reality of Java development. I still encounter systems that make pervasive use of Vector and Hashtableeven though Sun recommended use of collections framework classes (List/ArrayList, etc.) instead as of Java 1.2. That was over six years ago!

You will likely encounter raw collections for some time coming. You can quickly add some time safety to your code by using a checked wrapper. Suppose your existing mess of a system creates a list intended to hold Integer wrapper objects:

 List ages = new ArrayList(); 

Elsewhere in the code, in a method nested a few classes away, another bozo developer adds a new line of code:

 ages.add("17"); 

And even further away, code in another class is responsible for extracting age values from the collection:

 int age = ((Integer)ages.get(0)).intValue(); 

Oops! That line of legacy code results in a ClassCastException, since the first element in ages is a String, not an Integer. Sure, you will find the problem "real soon now," but you will have wasted valuable time in the process.

The best solution would be to parameterize references to ages in each of the three classes in question. Then the second developer wouldn't even be able to compile the code that inserts a string. But changing those classes might be beyond your control or there might be dozens of affected classes and not three.[2]

[2] And of course none of those dozens of classes will have tests. The vast majority of legacy systems have no tests.

Sun introduced a new set of methods to the Collections class that can help solve this problem with type safety. These methods create checked wrapper objects. A checked wrapper object delegates off to the actual collection object, much like the unmodifiable and synchronized wrappers. A checked wrapper ensures that an object passed to the collection is of the appropriate type, preventing inappropriate objects from being inserted into the collection.

Using a checked collection will at least constrain the exception to the point of error. In other words, the code that adds the bad data will generate the exception, not the code that attempts to extract it. This will make for quicker debugging efforts. You can make the change in one place:

 List ages = Collections.checkedList(new ArrayList(), Integer.class); 

When the Java VM executes this line of code, you receive an exception:

 java.lang.ClassCastException: Attempt to insert class java.lang.String element into collection with element type class java.lang.Integer 

A language unit test demonstrates this in toto:

 public void testCheckedCollections() {    List ages =       Collections.checkedList(new ArrayList(), Integer.class);    try {       ages.add("17");       fail("expected ClassCastException on invalid insert");    }    catch (ClassCastException success) {    } } 

Checked collections are not at all magical. The first argument to checkedList is the ArrayList object you are creating; it is bound to the Integer type. The second argument is a Class reference to the Integer type. Each time you invoke the add method, it uses the class reference to determine whether or not the passed parameter is of that type. If not, it throws a ClassCastException.

Checked wrappers require the redundancy of having to pass in a type (Integer.class) reference in addition to specifying a bind type (<Integer>). This is required because of erasure: the bind type information is not available to the list object at runtime. You can encapsulate unchecked wrappers in your own parameterized types, but you will then need to require clients to pass in the Class reference.

The Collection class provides checked collection wrappers for the types Collection, List, Map, Set, SortedMap, and SortedSet.

Even if you are writing J2SE 5.0 code, using checked collections can protect you from loose and fast developers beyond your sphere of influence. Some cowboy and cowgirl developers enjoy subverting the rules whenever they can. In the case of parameterized types, Java lets them.

You can cast an object of a parameterized type to a raw type. This allows you to store an object of any type in the collection. You will receive a compilation warning, but you can ignore the warning. Do so at your own peril. The results will generally be disastrous. This sort of strategy falls under the category of "don't do that!"

Using checked collections in 5.0 code can help solve this problem. The exceptions thrown will come from the sneaky code, letting you pinpoint the source of trouble.



Agile Java. Crafting Code with Test-Driven Development
Agile Javaв„ў: Crafting Code with Test-Driven Development
ISBN: 0131482394
EAN: 2147483647
Year: 2003
Pages: 391
Authors: Jeff Langr

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net