Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Parameterized type keys for Map

I am learning Java and am currently reading Joshua Bloch's Effective Java.

In Item 29, he discusses parameterized type keys for Map to create a type-safe heterogeneous map. Here is the code:

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");
        favorites.put(type, instance);
    }

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

He goes on to say that

A malicious client could easily corrupt the type safety of a Favorites instance, simply by using a Class object in its raw form. But the resulting client code would generate an unchecked warning when it was compiled.

I know that Class<T> would be erased to Class. But I am not sure how a malicious client can break the type safety at compile time. I tried various ways, but I always got a compiler error, as I expected.

Can someone please tell me what exactly Joshua Bloch meant in the line quoted above?

like image 252
MSS Avatar asked Dec 25 '22 08:12

MSS


1 Answers

A raw type is one that doesn't have generic information. Here's how you can defeat the type safety of the method:

Favorites favorites = new Favorites();
favorites.putFavorite((Class)Integer.class, "foo"); // no compile error

whereas this won't compile:

favorites.putFavorite(Integer.class, "foo"); // compile error

Because the type of the parameter is Class (and not Class<T>), the generic method parameter T can not be determined and type inference is switched off for that call. It's as if the code making the call is pre-generics, with which java is backwardly compatible (by ignoring generics).

Here's how you can guard against that problem:

public <T> void putFavorite(Class<T> type, T instance) {
    if (type == null)
        throw new NullPointerException("Type is null");
    if (!type.isInstance(instance)) // add check for instance type
        throw new IllegalArgumentException("Class/instance mismatch");
    favorites.put(type, instance);
}

or more brutally (as you can't be as informative with the error message), simply attempt the cast:

public <T> void putFavorite(Class<T> type, T instance) {
    if (type == null)
        throw new NullPointerException("Type is null");
    favorites.put(type, type.cast(instance)); // will throw ClassCastException
}

but that will only pick up a problem at runtime when the malicious code attempts to do damage, but it's still better than picking up the problem at usage time when some other client tries to use the dodgy instance.

like image 109
Bohemian Avatar answered Jan 10 '23 23:01

Bohemian