0

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).

Matei David
  • 2,322
  • 3
  • 23
  • 36
  • `uncheckedCast` will just end up throwing runtime CCEs anyhow if you have a mismatched type. Let's step back a moment: what's the problem you're trying to solve with this approach? Surely it's not just playing with type tokens for fun – Rogue Sep 12 '19 at 04:27
  • "If one were to put this uncheckedCast into a library, the function could easily be abused." there are far more dangerous things built-in that people can abuse. It's not your problem or responsibility if they do. [Here's](https://stackoverflow.com/questions/57865762/how-cast-to-known-generic-type-without-unchecked-warning/57866132) a related question I answered recently, with pretty much the exact same scenario. – Kayaman Sep 12 '19 at 06:47
  • @Rogue: I want to reduce boilerplate in a "proper" way. You are correct, the all powerful `uncheckedCast()` works, but has the negative side effect of moving some errors from compile time to run time. I'm looking for a way with the language to restrict `uncheckedCast` to something like ` U uncheckedCast(T t)`, so that the compile time check is not lost. – Matei David Sep 12 '19 at 13:54
  • @Kayaman I personally agree, I'm just getting some difficult time on this. This seems such a basic issue I am surprised I cannot find it (even the all-powerful cast) in a common library. I edited the post, I looked in Commons Lang, Guava, and Chronicle Core. – Matei David Sep 12 '19 at 13:57
  • @MateiDavid it's unlikely they would expose it to the outside as a general tool. Those unchecked casts are done inline and they're known to be safe. Having to fight the type system should be rare enough, so it doesn't make sense to have some `forceCast` functionality. – Kayaman Sep 12 '19 at 14:10
  • @Kayaman How often this is needed really depends on the kind of code you're writing. I've seen this pop up again and again, so I thought to bring it up here. I can see why the all-powerful cast is problematic, but the one that I'm asking for (the restricted cast that only changes a raw type into a generic one) is not as problematic, because usually there is nothing to really check at runtime when setting generic type parameters due to type erasure (except for special cases like `Class`). – Matei David Sep 12 '19 at 15:23

2 Answers2

2

You said

Also, I don't much care about this specific example, but about the general language problem.

but actually, this kind of problem should always be handled in relation to the actual problem you’re trying to solve. I’d go that far to say that this happens rarely enough, so a general utility method for doing unchecked casts regardless of the actual use case would not be justified.

Considering the SupplierMap, you said

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.

That’s nailing the problem and pointing to the clean solution. You have to create a relationship between the key and value, e.g.

class SupplierMap {
    static final class SupplierHolder<T> {
        final Class<T> keyClass;
        final Supplier<? extends T> valueSupplier;

        SupplierHolder(Class<T> keyClass, Supplier<? extends T> valueSupplier) {
            this.keyClass = keyClass;
            this.valueSupplier = valueSupplier;
        }
        @SuppressWarnings("unchecked") // does check inside the method
        <U> SupplierHolder<U> cast(Class<U> type) {
            if(type != keyClass) throw new ClassCastException();
            return (SupplierHolder<U>)this;
        }
    }

    Map<Class<?>, SupplierHolder<?>> map = new HashMap<>();

    <T> void put(Class<T> keyClass, Supplier<? extends T> valueSupplier) {
        map.put(keyClass, new SupplierHolder<>(keyClass, valueSupplier));
    }

    <T> Supplier<? extends T> get(Class<T> keyClass) {
        return map.get(keyClass).cast(keyClass).valueSupplier;
    }
}

Here, the unchecked type cast is inside a method that performs an actual check allowing the reader to be confident about the correctness of the operation.

That’s a pattern that is actually used in real life code. Which hopefully addresses your question “Have you seen this kind of an unchecked cast (even the all-powerful one above) in a common library?”. I don’t think that any library uses a “do-entirely-unchecked” method, but rather, they have methods with a visibility as narrow as possible and likely tailored to the actual use case.

Yes, that means “boilerplate”. Which is not bad for an operation that the developer should really spend some seconds, before proceeding.

Note that this can be expanded to examples completely working without using Class as token:

interface SomeKey<T> {}

class SupplierMap {
    static final class SupplierHolder<T> {
        final SomeKey<T> keyClass;
        final Supplier<? extends T> valueSupplier;

        SupplierHolder(SomeKey<T> keyToken, Supplier<? extends T> valueSupplier) {
            this.keyClass = keyToken;
            this.valueSupplier = valueSupplier;
        }
        @SuppressWarnings("unchecked") // does check inside the method
        <U> SupplierHolder<U> cast(SomeKey<U> type) {
            if(type != keyClass) throw new ClassCastException();
            return (SupplierHolder<U>)this;
        }
    }

    Map<SomeKey<?>, SupplierHolder<?>> map = new HashMap<>();

    <T> void put(SomeKey<T> keyClass, Supplier<? extends T> valueSupplier) {
        map.put(keyClass, new SupplierHolder<>(keyClass, valueSupplier));
    }

    <T> Supplier<? extends T> get(SomeKey<T> keyClass) {
        return map.get(keyClass).cast(keyClass).valueSupplier;
    }
}

Which allows more than one key with the same type:

enum MyStringKeys implements SomeKey<String> {
    SAY_HELLO, SAY_GOODBYE        
}
public static void main(String[] args) {
    SupplierMap m = new SupplierMap();
    m.put(MyStringKeys.SAY_HELLO, () -> "Guten Tag");
    m.put(MyStringKeys.SAY_GOODBYE, () -> "Auf Wiedersehen");
    System.out.println(m.get(MyStringKeys.SAY_HELLO).get());
    Supplier<? extends String> s = m.get(MyStringKeys.SAY_GOODBYE);
    String str = s.get();
    System.out.println(str);
}

The crucial part is that the now-unavoidable unchecked cast is still augmented with an actual check for the key validity. I’d never allow it without.

This doesn’t preclude scenarios where you really can’t check the correctness at all. But then, it is a good think that verbose artifacts like the @SuppressWarnings("unchecked") annotation indicate this right at the point where it is needed. A convenience method would only hide the problem, which is still there, even if we had a possibility to restrict its usage to generic types.


Answer to the previous revision of the question:

That’s actually easier than you think:

class ImplMap {
    Map<Class<?>, Class<?>> map;

    <T> void put(Class<T> keyClass, Class<? extends T> valueClass) {
        map.put(keyClass, valueClass);
    }

    <T> Class<? extends T> get(Class<T> keyClass) {
        final Class<?> implClass = map.get(keyClass);
        return implClass.asSubclass(keyClass);
    }
}

This is not an unchecked operation, as the method asSubclass really checks whether the implClass class is a subclass of keyClass. Assuming that the map has been populated via the put method only, without any unchecked operations, this test will never fail.

The only thing that differs, is the treatment of null, e.g. when the key was not present in the map. Unlike a cast, this would throw an exception, as it is a method invocation.

So if calling this method with absent keys is allowed and should result in null, it must be handled explicitly:

<T> Class<? extends T> get(Class<T> keyClass) {
    final Class<?> implClass = map.get(keyClass);
    return implClass == null? null: implClass.asSubclass(keyClass);
}

Note that likewise, the method

@SupressWarnings("unchecked")
<T> T uncheckedCast(Object o) {
    return (T) o;
}

is unnecessary if you have the Class object, as then, you can invoke cast on it.

E.g., the following would be a valid addition to your ImplMap class:

<T> T getInstance(Class<T> keyClass) {
    try {
        return keyClass.cast(map.get(keyClass).getConstructor().newInstance());
    } catch (ReflectiveOperationException ex) {
        throw new IllegalStateException(ex);
    }
}

As an additional note, registering interface implementation via configuration files sounds like you should have a look at the ServiceLoader API and the underlying mechanisms. See also the Creating Extensible Applications chapter of the Java tutorial.

Holger
  • 285,553
  • 42
  • 434
  • 765
  • I understand that `Class` objects carry information about their generic type parameter that makes them special to casts, but this is not true in general. You normally cannot tell at runtime if an empty `List>` is or is not a `List`. I don't care only about casting `Class`-es. How about e.g. a `SupplierMap` wrapping `Map,Supplier>>` supporting `supplierMap.put(I1.class, C1::new)` and `I1 i1 = supplierMap.get(I1.class).get()`? I'm not interested in this or that example, but in the root problem. – Matei David Sep 12 '19 at 14:48
  • So you're suggesting returning a `Supplier>` and moving the cast elsewhere. As I explained in the OP (even before any edits), I fell that the cast belongs conceptually inside `SupplierMap`. Put it another way: you don't know that the user code does with the `Supplier`. Maybe it applies `get()` immediately, but maybe it feeds the `Supplier` into some other functional composition. – Matei David Sep 12 '19 at 15:11
  • Sorry, I think I deleted your comment by mistake, I was trying to edit mine. Perhaps a mod can help? – Matei David Sep 12 '19 at 15:30
  • I really want `get()` to have the following signature ` Supplier extends T> get(Class keyClass)`. Why- Because the connection between the generic type parameters of key `Class` and value `Supplier` belongs to and is enforced during `put()` by `SupplierMap`. I don't want to return `Supplier>`, forcing the user code to do the cast whenever. The worry here is the compile time cast, which restores compile time type safety, not the runtime cast. – Matei David Sep 12 '19 at 15:34
  • Oh I see, you're suggesting `return () -> keyClass.cast(map.get(keyClass).get())` (or saving the looked up `Supplier` for later to not delay the lookup). Ok, can you help me with a better example, perhaps not involving `Class` at all? :) – Matei David Sep 12 '19 at 15:38
1

Why don't you try something like this, instead of declaring the types individually for functions, make the class generic and class handles this T types of Class instances.

class ImplMap<T> {

    // Values are already related here
    Map<Class<T>, Class<? extends T>> map;

    // Already compiler aware of the type.
    void put(Class<T> keyClass, Class<? extends T> valueClass) {
        map.put(keyClass, valueClass);
    }

    // Compiler already aware of the type just like with 'put'.
    Class<? extends T> get(Class<T> keyClass) {
        return map.get(keyClass);
    }
}

This doesn't involve unchecked cast since the type relationship already defined with the Map declaration and no need to SuppressWarning (Compiler doesn't warn).

Although compiler would warn about unchecked call when invoke the put and get function if you don't define type on ImplMap object creation as it not takes a type at all and if you define a type, you can put that type of keys only into that map which duplicates.

Sachith Dickwella
  • 1,388
  • 2
  • 14
  • 22
  • I really meant this as a language question. I'm not that interested in the `ImplMap`. But let's talk about it. What I want is a single `ImplMap` object that is populated, say, by reading a config, and holds info such as interface I1 is implemented by class C2, I2 by C2, etc. So during config parsing, you get `implMap.put(I1.class, C1.class)`, and at runtime you get `I1 i1 = implMap.get(I1.class).newInstance()`, resulting is `i1.getClass() == C1.class`. I'm not sure how your solution fits. What is `T` here? Isn't the map in one of your `ImplMap` objects confined to a single key? – Matei David Sep 12 '19 at 13:29
  • I added the `ImplMap` clarifications to the OP. – Matei David Sep 12 '19 at 13:58