Design Pattern - Typesafe heterogeneous container (Java)

1 - 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 of 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);

3 - 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

4 - Key

4.1 - Class object

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

See Class Literals as Runtime-Type Tokens

4.2 - 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.

5 - Example

5.1 - Type token

It works because Class was generified 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));
    }
}

5.2 - 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

6 - Documentation / Reference

code/design_pattern/typesafe_heterogeneous_container.txt · Last modified: 2017/09/20 22:34 by gerardnico