Use TypedCollection.decorate()
to create a Collection
that only accepts objects of a specified type. Supply an existing
Collection along with the Class that all elements should be constrained
to. TypedCollection will decorate
this existing Collection, validating
elements as they are added to a Collection. The following example creates a
Collection that will only accept
strings:
List existingList = new ArrayList( ); Collection typedCollection = TypedCollection.decorate( existingList, String.class ); // This will add a String typedCollection.add( "STRING" ); // And, This will throw an IllegalArgumentException typedCollection.add( new Long(28) );
Similarly, if you want to constrain keys and values to specified
types, pass a Map to TypedMap.decorate( ) method, specifying a
Class for both the key and the value.
In the following example, typedMap
only accepts String keys and Number values:
Map existingMap = new HashMap( ); Map typedMap = TypedMap.decorate( existingMap, String.class, Number.class ); // This will add a String key and a Double value typedMap.put( "TEST", new Double( 3.40 ) ); // Both of these throw an IllegalArgumentException typedMap.put( new Long(202), new Double( 3.40 ) ); typedMap.put( "BLAH", "BLAH" );
TypedCollection and TypedMap will decorate any existing Collection or Map and will throw an IllegalArgumentException if you try to add an
incompatible type.
A Map frequently contains keys
and values with consistent types; for instance, an application that
keeps track of Person objects by name
most likely has a personMap with
Person values and String keys. Rarely does a Map hold a wide diversity of types. Collections and Maps are not type-safe, and this lack of type
safety means that unexpected objects may be cast to incompatible types,
causing nasty ClassCastExceptions. It
is unlikely that every time you call get(
) and cast the resulting object, you catch ClassCastException; and, in most systems, it
is reasonable to assume that no one has put an incompatible type into a
Map. But, if a Map plays a central role in a critical
application, you may want an extra layer of validation; decorate your
maps with TypedMap to ensure that a
Map contains consistent types. There
is little penalty for decorating a Map as such, and if someone writes code to
insert invalid input, your application should fail immediately with an
IllegalArgumentException.
If your application uses a TypedMap, it is easier to track down defects.
If a ClassCastException is thrown
when calling get( ), you then need to
work backward to find out where the offending object was put into a
Map. An alternative is to validate
each object as it is added to a Map.
If the put( ) method throws IllegalArgumentException, it will be easier to
identify the offending code.
Java 5.0 adds the idea of generics—compile-time type safety for
any number of objects including Collections and Maps. But, if you are stuck with an older
version of the JDK, you can use Commons Collections to create a Collection that only accepts input of a
certain type. TypedSet, TypedBag, TypedList, TypedMap, TypedBuffer, TypedSortedSet, TypedSortedBag, TypedSortedMap all provide the same decoration
as TypedCollection, but they return a
specific interface; for example, TypedList decorates and returns a List, and TypedSet decorates and returns a Set. Example
5-13 demonstrates the use of the TypedList decorator to return a List instead of a Collection.
Example 5-13. Using TypedList to decorate a list
package com.discursive.jccook.collections.typed;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.collections.list.TypedList;
public class TypedListExample {
private List hostNames;
public static void main(String[] args) {
TypedListExample example = new TypedListExample( );
example.start( );
}
public void start( ) {
// Make sure that items added to this
hostNames = TypedList.decorate( new ArrayList( ), String.class );
// Add two String objects
hostNames.add( "papp01.thestreet.com" );
hostNames.add( "test.slashdot.org" );
// Try to add an Integer
try {
hostNames.add( new Integer(43) );
} catch( IllegalArgumentException iae ) {
System.out.println( "Adding an Integer Failed as expected" );
}
// Now we can safely cast without the possibility of a
// ClassCastException
String hostName = (String) hostNames.get(0);
}
}If a List decorated with
TypedList encounters an invalid
object, the add( ) method will throw
an IllegalArgumentException.
A Typed<X> decorated
Collection will not be able to
provide the compile-time type safety of Java 5.0's generics, but it
will enforce a restriction on what it can accept—it is up to you to
catch the runtime exception.
TypedMap allows you to
constrain both the keys and values of a map. TypedMap.decorate( ) takes three parameters:
the Map to decorate, the key Class, and the value Class. To create a Map that only constrains key types, pass in a
null value for the value type. To
create a Map that only validates the
type of the value, pass in a null for
the key type. Example 5-14 uses
TypedMap.decorate( ) to create a
Map that only accepts String keys and Number values.
Example 5-14. Decorating a map with TypedMap
package com.discursive.jccook.collections.typed;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.collections.map.TypedMap;
public class TypedMapExample {
private Map variables;
public static void main(String[] args) {
TypedMapExample example = new TypedMapExample( );
example.start( );
}
public void start( ) {
// Make sure that items added to this
variables =
TypedMap.decorate( new HashMap( ), String.class, Number.class );
// Add two String objects
variables.put( "maxThreads", new Integer(200) );
variables.put( "minThreads", new Integer(20) );
variables.put( "lightSpeed", new Double( 2.99792458e8 ) );
// Try to add a String value
try {
variables.put( "server", "test.oreilly.com" );
} catch( IllegalArgumentException iae ) {
System.out.println( "Adding an String value Failed as expected" );
}
// Try to add an Integer key
try {
variables.put( new Integer(30), "test.oreilly.com" );
} catch( IllegalArgumentException iae ) {
System.out.println( "Adding an Integer key Failed as expected" );
}
// Now we can safely cast without the possibility of a ClassCastException
Number reading = (Number) variables.get("lightSpeed");
}
}Java 5.0 has added generics—a welcome addition. For more information about generics, look at the release notes for Java 5.0 at http://java.sun.com/j2se/1.5.0/docs/relnotes/features.html#generics.
For more information about the decorator design pattern, read the classic Design Patterns: Elements of Reusable Object-Oriented Software, by Erich Gamma et al., or take a look at this onJava.com article by Budi Kurniawan: http://www.onjava.com/pub/a/onjava/2003/02/05/decorator.html, which deals with the decorator pattern as applied to Java Swing development, but this pattern also has relevance outside of a GUI development context.
This TypedCollection decorator
is a specialized version of a PredicatedCollection. Type-safety is
implemented through the use of an InstanceofPredicate, and the next recipe
discusses the use of a PredicatedMap.
