Search code examples
javagenericstypescastingunchecked

Java - is mixed type container (non-generic class) possible without unchecked cast?


I am trying to make a simple storage class, that will store different classes instances. The only almost correct way I managed to do is with unchecked type casting.

HashSet<T> result = (HashSet<T>) storage.get(s);

Can it be done without unchecked casting and without making the class generic (class Storage<T> { })?

import java.util.*;
import org.junit.*;

class Tests {
    @Test
    public static void main (String[] args) {
        Storage storage = new Storage();

        HashSet<Child1> child1Set = storage.get("child1");
        HashSet<Child1> duplicateChild1Set = storage.get("child1");

        Assert.assertNotNull(child1Set);
        Assert.assertSame(child1Set, duplicateChild1Set);

        HashSet<Child2> child2Set = storage.get("child2");

        Assert.assertNotNull(child2Set);
        Assert.assertNotSame(child1Set, child2Set);
    }
}

class Storage {

    public Map<String, HashSet<? extends Parent>> storage = new HashMap<>();

    public <T extends Parent> HashSet<T> get(String s) {
        HashSet<T> result = (HashSet<T>) storage.get(s);
        if (result == null) {
            result = new HashSet<>();
            storage.put(s, result);
        }
        return result;
    }
}

class Parent { }

class Child1 extends Parent { }

class Child2 extends Parent { }

Solution

  • You can do it using Class objects as keys, rather than Strings. Here is a short example. For simplicity, I have not included extends Parent - you can put those back.

    public final class Storage {
    
        private final Map<Class<?>, Set<?>> storage = new HashMap<>();
    
        public <T> Set<T> get(Class<T> s) {
            Set<T> result = (Set<T>) storage.get(s); // Unchecked cast
            if (result == null) {
                result = new HashSet<>();
                storage.put(s, result);
            }
            return result;
        }
    }
    

    It is not possible to eliminate the unchecked cast in a mixed type container like this. There is no way to specify that if the key has type Class<T>, the value has type Set<T>. However, as long as the user of the Storage class does not ignore any type safety warnings, this is completely type safe.

    To use the class you can do storage.get(Double.class).add(4.2);, for example.