Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generics and Serializable

We created an abstract class that we use for dealing with Redis (set/get values), that looks like follows:

public abstract class AbstractCachedSupport<T extends Serializable> {
    protected T get(CacheKey key, Supplier<T> supplier) {...}
    // ...
}

What I'm not happy about is that we cannot use interfaces like List, Map when extending this class:

public class CachedMap extends AbstractCachedSupport<Map<String, Integer>>

because they do not extend Serializable, so we have to always use concrete classes:

public class CachedMap extends AbstractCachedSupport<HashMap<String, Integer>>

Needless to say, this has its share of problems, when migrating from one concrete class to another for instance. It's also not something I would call best practice, but maybe that's just me.

An alternative that gives us the flexibility to work with interfaces would be to remove the bounded typing and check at runtime whether T extends Serializable:

public abstract class AbstractCachedSupport<T> {
    public AbstractCachedSupport() {
        final Class<T> type = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[0];
        if (!Serializable.class.isAssignableFrom(type)) {
            throw new RuntimeException("T must extend Serializable");
        }
    }

    protected T get(CacheKey key, Supplier<T> supplier) {...}
    // ...
}

This leaves us without compile time checking on T extending Serializable, not a nice thing either.

Do you know of any other way how we could solve this elegantly? Would you rather use the first one (bounded type parameter) or the second (runtime checking only)?

A compromise would be to go for the first and always use a container class for holding the collection:

public class IntegerParamsContainer implements Serializable {
    private static final long serialVersionUID = 1L;
    private Map<String, Integer> map;
}

public class CachedMap extends AbstractCachedSupport<IntegerParamsContainer>

But this has in the end the same problem as the second: there is no compile time checking, the responsibility lies on the shoulders of the developers to always use collections that implement Serializable.

Edit: Some of the classes extending AbstractCachedSupport are Spring (version 4.1 currently) component classes and they are not called CachedMap or something similar, rather CityAutocompleteDataBean, WrParamsDataBean etc.. If we add generics to these component classes, we'll end up with declarations such as:

@Inject
private CityAutocompleteDataBean<ArrayList<String>>;
@Inject
private WrParamsDataBean<HashMap<String, WrData>>;

as opposed to

@Inject
private CityAutocompleteDataBean;
@Inject
private WrParamsDataBean;

The reason for using <ArrayList<String>> and <HashMap<String, WrData>> will escape most developers when they'll see such lines of code. I also find it rather ugly, considering where we started and what we use this for.

Nonetheless, this works as requested by me, thank you Jesper.

like image 241
Marius Burz Avatar asked Apr 26 '15 15:04

Marius Burz


1 Answers

You can do it using the following syntax:

public abstract class AbstractCachedSupport<T extends Serializable> {
    // ...
}

public class CachedMap<T extends Map<String, Integer> & Serializable>
        extends AbstractCachedSupport<T> {
    // ...
}

This means that the type T must implement Map<String, Integer> and also Serializable.

Then you could use CachedMap with a specific Map implementation that implements Serializable:

CachedMap<HashMap<String, Integer>> cachedMap = new CachedMap<>();
like image 60
Jesper Avatar answered Oct 06 '22 01:10

Jesper