I'm looking for a "proper" way to reduce the Java boilerplate involved in retrieving/modifying the generic type arguments at compile time. Usually, this boilerplate involves:
- Using
@SuppressWarnings("unchecked")
. - Spelling out explicitly the target generic type arguments.
- Usually, creating an otherwise useless local variable just so that the supression can be applied to that staement only.
As a theoretical example, suppose I want to keep a map of Class
to Supplier
such that for each keyClass
, its associated valueSupplier
produces objects extending keyClass
.
Edit 2: I changed the example from a map of Class
to Class
, to a map of Class
to Supplier
, because (the value) Class
objects are special with respect to casts, and the original example had another solution not involving unchecked casts (thanks @Holger). Again, I'm only adding an example to illustrate the problem, I don't need to solve any particular example.
Edit 1: More precisely, a single SupplierMap
object is populated say from a config file, and holds info such as "objects implementing interface I1 are provided by supplier S1", "I2 by S2", and so on. At runtime, we get calls such as I1 i1 = supplierMap.get(I1.class).get()
which should produce an object with i1.getClass() == C1.class
. I'm not interested in fixes/shortcuts, e.g. moving the cast to where it does not belong, such as having get()
return Supplier<Object>
. The cast belongs conceptually inside the SupplierMap
. Also, I don't much care about this specific example, but about the general language problem.
With SupplierMap
, I don't believe there is a way to capture the key-value generic parameter relation in Java so that the get()
does not involve an unchecked compile-time cast. Concretely I could have:
class SupplierMap {
// no way to say in Java that keys and values are related
Map<Class<?>, Supplier<?>> map;
// can check the relation at compile time for put()
<T> void put(Class<T> keyClass, Supplier<? extends T> valueSupplier) {
map.put(keyClass, valueSupplier);
}
// but not for get()
<T> Supplier<? extends T> get(Class<T> keyClass) {
@SuppressWarnings("unchecked")
final Supplier<? extends T> castValueSupplier = (Supplier<? extends T>) map.get(keyClass);
return castValueSupplier;
}
}
As an alternative, one could have:
@SupressWarnings("unchecked")
<T> T uncheckedCast(Object o) {
return (T) o;
}
<T> Supplier<? extends T> get(Class<T> keyClass) {
return uncheckedCast(map.get(keyClass));
}
That looks much better, but the problem is that uncheckedCast
is arguably too powerful: it can potentially cast anything into anything else at compile time (by hiding warnings). At runtime we'd still get CCE's, but that's not the point here. (The argument goes that ...) If one were to put this uncheckedCast
into a library, the function could be abused to hide problems otherwise detectable at compile-time.
Is there a way to define such a similar unchecked cast function so that the compiler can enforce that it is only used to change generic type parameters?
I tried:
// error at T<U>: T does not have type parameters
<T, U> T<U> uncheckedCast(T t) {
return (T<U>) t;
}
also
<T, U extends T> U uncheckedCast(T t) {
return (U) t;
}
void test() {
Class<?> aClass = String.class;
// dumb thing to do, but illustrates cast error:
// type U has incompatible bounds: Class<capture of ?> and Class<Integer>
Class<Integer> iClass = uncheckedCast(aClass);
}
Edit: Have you seen this kind of an unchecked cast (even the all-powerful one above) in a common library? I looked in Commons Lang and Guava, but the only one I could find is Chronicle Core's ObjectUtils.convertTo()
: there, passing eClass == null
is equivalent to the all-powerful uncheckedCast
, except that it also produces an undesired @Nullable
(that is used by other branches).