Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic Type cast [duplicate]

I have the following class (simplified but still a working example):

class Test<T> {
    List<T> l = new ArrayList<>();

    public Test() {
    }

    public void add(Object o) {
        l.add((T)o);
    }
}

And the test code:

Test<Double> t = new Test<>();
t.add(1);
t.add(1.2);
t.add(-5.6e-2);
t.add("hello");

Everything is working fine, and that's not what I was expecting. Shouldn't the add method throw a ClassCastException? If I add a get method that's more or less the same thing:

    public T get(int i) {
        return l.get(i);
    }
.../...
t.get(1);             // OK.
t.get(3);             // OK (?)
Double d = t.get(3);  // throws ClassCastException

Why is it only on variable assignment that the exception is thrown? How can I enforce type consistency if the (T) cast doesn't work?

like image 596
gregseth Avatar asked Aug 12 '15 09:08

gregseth


2 Answers

Shouldn't the add method throw a ClassCastException?

No, it shouldn't (although I wish it did). Long story short, Java implementation of generics discards type information after compiling your code, so List<T> is allowed to take any Object, and the cast inside your add method is not checked.

Why is it only on variable assignment that the exception is thrown?

Because the cast to Double there is inserted by the compiler. Java compiler knows that the return type of get is T, which is Double, so it inserts a cast to match the type of the variable d, to which the result is being assigned.

Here is how you can implement a generic-safe cast:

class Test<T> {
    private final Class<T> cl;
    List<T> l = new ArrayList<>();

    public Test(Class<T> c) {
        cl = c;
    }

    public void add(Object o) {
        l.add(cl.cast(o));
    }
}

Now the cast is performed by a Class<T> object, so you will get a ClassCastException on an attempt to insert an object of an incorrect type.

like image 131
Sergey Kalinichenko Avatar answered Oct 19 '22 22:10

Sergey Kalinichenko


As an alternative solution you can use Collections.checkedList:

class Test<T> {
    List<T> l;

    public Test(Class<T> c) {
        l = Collections.checkedList(new ArrayList<T>(), c);
    }

    public void add(Object o) {
        l.add((T) o);
    }
}

This way you will get the following exception:

Exception in thread "main" java.lang.ClassCastException: Attempt to insert 
  class java.lang.Integer element into collection with element type class java.lang.Double
    at java.util.Collections$CheckedCollection.typeCheck(Collections.java:3037)
    at java.util.Collections$CheckedCollection.add(Collections.java:3080)
    at Test.add(Test.java:13)
like image 44
Tagir Valeev Avatar answered Oct 19 '22 23:10

Tagir Valeev