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.
Collection
s and Map
s are not
type-safe, and this lack of type safety means that unexpected objects
may be cast to incompatible types, causing nasty
ClassCastException
s. 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 Collection
s and
Map
s. 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
.
3.133.132.99