Design Pattern - Typesafe heterogeneous container (Java)

Card Puncher Data Processing

About

Typesafe heterogeneous container pattern. Container meaning a list of objects.

A container instance is:

  • typesafe: when it will never return an Integer when you ask it for a String.
  • heterogenous: when all the keys in the collection can be of different types.

The implementation idea is to parameterize the key instead of the container. The generic class type Class<?> or Class.class will be the key part of the key in the heterogenous object collection.

private Map<Class<?>, Object> heterogenousObjects = new HashMap<Class<?>, Object>();

The generic type system will guarantee that the type of the value agrees with its key. In the below example, we add the heterogenousObject and the type have the same type T.

public <T> void putHeterogenousObject(Class<T> type, T heterogenousObject);

Example

  • A database row can have arbitrarily many columns, and it would be nice to be able to access all of them in a typesafe manner. You need one type parameter for each column.
  • A variables container

Key

Class object

A Class literal used as key is called a type token.

See Class Literals as Runtime-Type Tokens

Custom type

A custom key type can also be used.

For example:

  • A DatabaseRow type representing a database row (the container),
  • and a generic type Column<T> as its key.

Example

Type token

It works because Class was transformed as generic in release 1.5.

The type of a class literal is no longer simply Class, but Class<T>. For example:

  • String.class is of type Class<String>,
  • and Integer.class is of type Class<Integer>.
  • API
public class Favorites {
    public <T> void putFavorite(Class<T> type, T instance);
    public <T> T getFavorite(Class<T> type);
}
  • Client
public static void main(String[] args) {
    Favorites f = new Favorites();
    f.putFavorite(String.class, "Java");
    f.putFavorite(Integer.class, 0xcafebabe);
    f.putFavorite(Class.class, Favorites.class);
    String favoriteString = f.getFavorite(String.class);
    int favoriteInteger = f.getFavorite(Integer.class);
    Class<?> favoriteClass = f.getFavorite(Class.class);
    System.out.printf("%s %x %s%n", favoriteString,
        favoriteInteger, favoriteClass.getName());
}
  • Container Implementation
public class Favorites {
    private Map<Class<?>, Object> favorites =
        new HashMap<Class<?>, Object>();

    public <T> void putFavorite(Class<T> type, T instance) {
        if (type == null)
            throw new NullPointerException("Type is null");
        
        // Achieving runtime type safety with a dynamic cast
        // Same trick can be found in the following implementations
        // checkedSet, checkedList, checkedMap, and so forth.
        favorites.put(type, type.cast(instance));

    }

    public <T> T getFavorite(Class<T> type) {
        return type.cast(favorites.get(type));
    }
}

Custom Type

  • The custom type
public class Column<T> {

    private final Class<T> type;

    public Column(Class<T> type) {
        this.type = type;
    }

    public T cast(Object obj) {
        return obj == null ? null : type.cast(obj);
    }
}
  • The container implementation
import java.util.HashMap;
import java.util.Map;

public class DatabaseRow {


    private Map<Column<?>, Object> columns =
            new HashMap<Column<?>, Object>();

    public <T> void putColumn(Column<T> type, T instance) {
        if (type == null)
              throw new NullPointerException("Type is null");

        // Achieving runtime type safety with a dynamic cast
        // Same trick can be found in the following implementations
        // checkedSet, checkedList, checkedMap, and so forth.
        columns.put(type, instance.getClass().cast(instance));

    }

    public <T> T getColumn(Column<T> type) {

        return type.cast(columns.get(type));
    }

}
  • The client
public class DatabaseClient {

    public static void main(String[] args) {
        Column<Integer> integerColumn = new Column<Integer>(Integer.class);
        Column<String> stringColumn = new Column<String>(String.class);

        DatabaseRow databaseRow = new DatabaseRow();
        databaseRow.putColumn(integerColumn, 3);
        databaseRow.putColumn(stringColumn, "3");

        System.out.println("The integer + 1: "+ (databaseRow.getColumn(integerColumn)+1));
        System.out.println("The string: "+ databaseRow.getColumn(stringColumn));

    }
}
  • The output
The integer: 4
The string: 3

Documentation / Reference





Discover More
Simple Class
Java - Class (Definition)

A java/lang/Classclass provides the blueprint for objects; you create an object from a class. All classes are derived from the Object class. A class declaration names the class and encloses the class...



Share this page:
Follow us:
Task Runner