5.13. Creating Typed Collections and Maps

Problem

You need to guarantee that a Collection or a Map only contains objects of a certain type.

Solution

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.

Discussion

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.

Tip

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");
        
    }
}

See Also

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.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.133.132.99