RudolfW
RudolfW

Reputation: 594

How to ensure parametrized type correctness at runtime in Java?

I have a simple generic list class. I'm trying to achieve this: given a list instance of which we know only that it contains instances of a specific subclass of a class, and given an instance of this class, add this instance to the list if it is instance of the contained (subclass) type, otherwise throw an exception (eg. ClassCastException). I tried the following:

class MyList<T>
{
    private Class<T> genericClass;
    private List<T> list = new ArrayList<>();

    public MyList(Class<T> genericClass)
    {
        this.genericClass = genericClass;
    }

    public void add(T elem)
    {
        list.add(elem);
    }

    //...

    public Class<T> getGenericParamClass()
    {
        return genericClass;
    }
}

class A{}
class B extends A{}
class C extends A{}

class Program
{
    public static void main(String... args)
    {
        MyList<B> list1 = new MyList<>(B.class);
        MyList<C> list2 = new MyList<>(C.class);

        MyList<? extends A> ls = checkStuff() ? list1 : list2;

        ls.add(ls.getGenericParamClass().cast(lotsOfStuff())); //ERROR ?!!
    }

    static boolean checkStuff()
    {
        Random random = new Random();
        return random.nextBoolean();
    }

    static A lotsOfStuff()
    {
        return new B();
    }
}

I thought that given a Class object whose type parameter is the same as the type of a parameter of a method, I would be able to cast something using the former to be able to pass it to the latter. Alas, it seems I cannot: I get a compile-time error!

I could throw generics out the window, go full unchecked and just say:

A val = lotsOfStuff();
if (myList.getGenericParamClass().isInstance(val))
    ls.add(val)
else
    throw new SomeException();

But, that would probably create more problems than it would solve, and also it would really bug me.

Am I missing something here, or is it simply not possible the way I thought it out?

Edit: I understand fully well why something like this cannot work:

List<? extends Number> abc=new ArrayList<Integer>();
abc.add(new Integer(10));

But in my mind, the following transitivity holds: the type of the parameter of add() Is-The-Same-As the type parameter of MyList Is-The-Same-As the type parameter of the Class returned by getGenericParamClass() Is-The-Same-As the return type of the cast() method of that Class. I (as a human) can know that those unknown types are the same, because I am getting them from the same object.

Is there a fault in my logic, or is this a limitation of Java?

Upvotes: 0

Views: 135

Answers (1)

meriton
meriton

Reputation: 70574

The compilation error is:

The method add(capture#2-of ? extends A) in the type MyList is not applicable for the arguments (capture#3-of ? extends A)

To understand that message, recall that ? extends A stands for an unknown type that is a subtype of A. That compiler can not know that the ? extends A returned by lotsOfStuff() is the same (or a subtype of) the ? extends A that the MyList.add method expects, and in fact, your program does not ensure that this is the case (because lotsOfStuff() always returns a B, even if it should be added to a list of C.)

To express that the two are of the same type, we must use a type parameter. The easiest way to get one is to move the code doing the casting and throwing into class MyList<T> (which already has a suitable type parameter), for instance by adding the following method:

void addOrThrow(Object o) {
    add(genericClass.cast(o));
}

Upvotes: 2

Related Questions