Extending the Typesafe Heterogeneous Container Pattern

In Effective Java, Joshua Bloch presents a pattern called the typesafe heterogeneous container (lovingly abbreviated THC – see item #33). Seeing this pattern for the first time really blew my mind, and I immediately wanted to try to find a way to extend it for more general-purpose use. In this post, I’ll present the pattern itself in case you’ve never seen it as well as an easy extension that can give your API a serious upgrade.

The Problem

Let’s start out by considering the problem scenario. For some classes that have a large quantity of properties, having getters and setters for each one can be cumbersome. To keep the API simple, the class may only expose a single setter and getter and define the properties in a separate place like an enum (or worse – string constants (EJ 62)). Here’s how the API/implementation would look:

public class User
{
    private final Map<UserProperty, Object> properties = new EnumMap<>(UserProperty.class);

    public Object getProperty(UserProperty property)
    {
        return properties.get(property);
    }

    public void setProperty(UserProperty property, Object value)
    {
        properties.put(property, value);
    }
}
public enum UserProperty
{
    NAME,

    AGE,

    BIRTHDAY,

    PHONE_NUMBER,
    
    ...
}

I’m sure everyone has dealt with APIs like this before. The problem with this approach is glaring – nothing is type safe! Client code is cluttered with type casts, and even if you document the types that should correspond to each property, there’s nothing the compiler can do to help you enforce that. Instead of catching an error at compile time, you are leaving your clients at risk of runtime ClassCastExceptions.

What can we do about this? Java has generics, but they’re typically used to parameterize a container or collection so that the entire class adheres to a single type. Enter …

The Typesafe Heterogeneous Container Pattern

The THC pattern flips conventional Java generics around by parameterizing the key instead of the actual container. Here’s the simple example from Effective Java:

public class Favorites
{
    private final Map<Class<?>, Object> favorites = new HashMap<>();

    public <T> void putFavorite(Class<T> type, T instance)
    {
        favorites.put(Objects.requireNonNull(type), type.cast(instance));
    }

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

And here’s how it would work in action:

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

Ah, type-safety restored!

But you’re probably thinking, "what if I wanted to store more than one String or Integer?" In our User example above, there will likely be several properties that have the same type. With Favorites, we can only store one instance of a particular type. That’s where we need to extend this pattern one step further.

THC Extension

Remembering that THC is all about parameterizing the keys, what we’ll need to do is associate a type with each of our UserProperty instances. Unfortunately, Java doesn’t allow generic types on enum constants, so we’ll have to go another route. This means we’ll lose all the benefits that come for free with enums (being able to iterate over all with values(), performance improvements of EnumMap, etc.), but in the end it’ll all be worth it.

Let’s start by creating a simple class to represent our keys:

@Immutable
public final class UserProperty<T>
{
    private final String name;

    private final Class<T> type;

    UserProperty(String name, Class<T> type)
    {
        this.name = checkNotNull(name);
        this.type = checkNotNull(type);
    }

    public Class<T> getType()
    {
        return type;
    }

    @Override
    public String toString()
    {
        return name;
    }

    @Override
    public boolean equals(Object o)
    {
        if (o == this)
        {
            return true;
        }
        if (!(o instanceof UserProperty))
        {
            return false;
        }
        UserProperty<?> other = (UserProperty<?>) o;
        return other.name.equals(name) && other.type.equals(type);
    }

    @Override
    public int hashCode()
    {
        int result = name.hashCode();
        result = 31 * result + type.hashCode();
        return result;
    }
}

Unless you want clients to be able to create and define their own properties, be sure to make the constructor package-private.

In a separate non-instantiable class, we’ll define each of our properties along with their types.

public final class UserProperties
{
    public static final UserProperty<String> NAME = new UserProperty<>("Name", String.class);

    public static final UserProperty<Integer> AGE = new UserProperty<>("Age", Integer.class);

    public static final UserProperty<LocalDate> BIRTHDAY = new UserProperty<>("Birthday", LocalDate.class);

    public static final UserProperty<PhoneNumber> PHONE_NUMBER = new UserProperty<>("Phone Number", PhoneNumber.class);
    
    ...

    private UserProperties()
    {
    }
}

You can use your own custom value classes here if you want, as I’ve done with the PHONE_NUMBER property.

With this in place, we can change our User class to a new and improved type-safe version of itself:

public class User
{
    private final Map<UserProperty<?>, Object> properties = new HashMap<>();

    public <T> T getProperty(UserProperty<T> property)
    {
        return property.getType().cast(properties.get(property));
    }

    public <T> void setProperty(UserProperty<T> property, T value)
    {
        properties.put(checkNotNull(property), property.getType().cast(value));
    }
}

Caveat

If this pattern intrigues you, I’d encourage you to read more about it in Effective Java. One of the limitations that Joshua Bloch points out is that you can’t use non-reifiable types like List<String> as your value type.

Unfortunately, there is not an easy fix for this limitation. One way around this is to create your own "wrapper" type using composition (EJ 18) to use in place of the non-reifiable type you want to use. For example, we could create:

@Immutable
public final class PermissionList
{
    private final ImmutableList<String> permissions;

    public PermissionList(Iterable<String> permissions)
    {
        this.permissions = ImmutableList.copyOf(permissions);
    }

    public ImmutableList<String> getPermissions()
    {
        return permissions;
    }

    ...
}

The client code works out just about as easy as it would for any other type.

User testUser = new User();
testUser.setProperty(UserProperties.PERMISSIONS, new PermissionList(ImmutableList.of("Admin", "Global")));

ImmutableList<String> permissions = testUser.getProperty(UserProperties.PERMISSIONS).getPermissions();

Conclusion

If you ever come across a class that deals with Object return or parameter types, the THC pattern plus this extension can make a world of difference. Give it a try and let me know what you think!

Leave a Reply