Mehran
Mehran

Reputation: 16901

The right way to wrap a non-generic class with a generic one

I'm trying to wrap (and simplify) the MapDB library in my project like this:

public class MapDbPersistentStorage<V>
{
    private DB db;
    private HTreeMap<String, V> storage;

    public MapDbPersistentStorage(String filename)
    {
        db = DBMaker
                .fileDB(filename)
                .fileMmapEnable()
                .make();

        storage = (HTreeMap<String, V>) db
                .hashMap("section", Serializer.STRING, Serializer.LONG)
                .createOrOpen();
    }

    public V get(String key, V defaultValue)
    {
        return storage.getOrDefault(key, defaultValue);
    }

    public void set(String key, V value)
    {
        storage.put(key, value);
    }

    public void close()
    {
        db.close();
    }
}

As it's been pointed out, the problem is the Serializer.LONG part (it's supposed to be of type V). I need to come up with a serializer based on the generic type provided. What's the right way to do so?

Upvotes: 0

Views: 369

Answers (1)

Jorn Vernee
Jorn Vernee

Reputation: 33895

Java uses erasure to implement it's generics. At runtime, your class has no idea what V you used to instantiate it with. That information is not retained at runtime.

So if you need it at runtime, you're going to have to manually retain it. But in your current program, the compiler has no idea how the type Long should relate to Serailizer.Long.

To be (somewhat) typesafe, you could wrap the Serializer in a generic class or interface which generic type corresponds to the type encoded by the serialzer. Effectively provide a way to link the 2 type systems.

interface GenericSerializer<T> extends Supplier<Serializer> {

    public static GenericSerializer<Long> ofLong() {
        return () -> Serializer.Long;
    }

    ...
}

Then in your class:

public class MapDbPersistentStorage<V> {
    private DB db;
    private HTreeMap<String, V> storage;
    private final Serializer ser;

    public MapDbPersistentStorage(GenericSerializer<V> serFactory) {
        this.ser = serFactory.get();
    }

    public MapDbPersistentStorage(String filename) {
        db = DBMaker
                .fileDB(filename)
                .fileMmapEnable()
                .make();

        storage = (HTreeMap<String, V>) db
                .hashMap("section", Serializer.STRING, ser)
                .createOrOpen();
    }

    ...
}

Usage:

MapDbPersistentStorage<Long> m = new MapDbPersistentStorage<>(GenericSerializer.ofLong());

Where the compiler can check that the type of the GenericSerializer corresponds to that of the MapDbPersistentStorage.

Upvotes: 1

Related Questions