Is casting the class returned by getClass() of a generic instance type always safe in Java?
Asked Answered
N

1

7

I am not sure how to word the title of this question in a concise way. There are a few related questions I have found, for instance this one, but none of them seem to answer the question I have explicitly.

But essentially what I am asking is this:

Consider the following code


static <A, B> Class<? extends A> getLeftClass(Pair<A, B> tuple) {
   A left = tuple.getLeft();
   return left.getClass();
}

As is, this code does not compile. The compilation fails with the error Type mismatch: cannot convert from Class<capture#20-of ? extends Object> to Class<? extends A>

I think this is essentially because getClass returns a type Class<? extends Object> but the compiler is expecting Class<? extends A>.

A solution is to cast the class as follows:


static <A, B> Class<? extends A> getLeftClass(Pair<A, B> tuple) {
   A left = tuple.getLeft();
   return (Class<? extends A>) left.getClass();
}

This compiles. However, there is a warning because of an unchecked cast.

My question is, is the warning justified? Is there a legitimate reason not to do this? Or is this just a case where the compiler can't verify that it is correct, but it will always work as long as tuple.getLeft() is indeed an instance of A?


By the way, the Pair class is this one, from the Apache commons library

Nata answered 16/10, 2020 at 15:23 Comment(8)
Your assumption looks right to me. I think it's a quirk of Java's generics that x.getClass() doesn't declare its return type as Class<? extends X> anyway; especially since the compiler sometimes treats it as though it does.Melinamelinda
Try your original code with a return type of Class<A>. I think extends is wrong. If anything the class type should be a super class of A, not a sub class.Micronutrient
@Micronutrient A variable of type A can hold an object at runtime of type A or any subtype of A. Hence Class<? extends A>.Melinamelinda
@Melinamelinda I don't think generics are covariant like that. A is not a variable but a type parameter. Its type can only be A. (At least, without any constraints, I don't think A will ever resolve to anything but itself.)Micronutrient
@Melinamelinda where would X be taken from, though? getClass is declared in java.lang.ObjectKazan
@Micronutrient If you have a Pair<A,B> then left is expected to be an instance of A. But an instance of A might be a direct instance of some subtype of A. So its class would be ? extends A.Melinamelinda
@Kazan Yes, that's why its declared return type is Class<?>; but its actual return type is Class<? extends X> where X is whatever the type is of the reference you're calling getClass() on.Melinamelinda
@Melinamelinda I suppose, you mean this rule, "The type of a method invocation expression of getClass is Class<? extends |T|>, where T is the class or interface that was searched for getClass (§15.12.1) and |T| denotes the erasure of T (§4.6)." The erasure point might be annoying but as my answer demonstrates, dropping that rule would result in unsafe code.Praxiteles
P
3

The warning is justified, as it allows in turn other unsafe operations that would not cause a warning. The type parameter of Class allows you to perform dynamic runtime casts and instantiations and the type safety of those operations depends on the validity of the type parameter.

In other words, your method allows the following operation:

Pair<List<String>,?> p = Pair.of(new ArrayList<>(), null);

List<Integer> listOfI = new ArrayList<>(Arrays.asList(1, 2, 3));
List<String> listOfS = getLeftClass(p).cast(listOfI);
listOfS.set(1, "foo");

This situation is called heap pollution and Java's generic type system guarantees that this situation can not occur with warning free source code. You get a warning and have the risk.

Likewise, we could do:

List<String> stringList = getLeftClass(p)
    .getConstructor(Collection.class).newInstance(listOfI);
assert stringList.get(0) instanceof String;// will fail

There are similar 3rd party libraries, e.g. deserializers for XML or JSON, with similar assumptions about the type safety when being provided with a Class object as parameter to describe the assumed return type (or a component of the result).

Praxiteles answered 17/10, 2020 at 9:3 Comment(2)
thank you for proving me wrong. Now the problem is that getLeftClass is a bit useless for the OP, isn't it?Kazan
@Kazan I encountered scenarios where I needed such unchecked casts on Class objects myself; the important point is to keep the cast and the subsequent use of the object in the same code unit, as close as possible, so that the reason and implications remain recognizable. A utility method that hides the cast would be counter-productive.Praxiteles

© 2022 - 2024 — McMap. All rights reserved.