Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Simplest way to cast a Class<T> to a Class<E extends Enum<E>> without losing type information

I have a method createFoo() that creates instances of Foo<T> using the Class<T> instance for that T. Now I want to extend that method to forward calls that are made using an enum type to the method createEnumFoo(). Calling the second method from the first one seems to be non-trivial. Below is an example of how I managed to do it using two unchecked casts and an extra method, all of which I would like to get rid of.

The method castEnumType() is required because I couldn't find a way to cast a Class<?> to a Class<E extends Enum<E>> without having the E bound somewhere. This involves an unchecked cast because I have not found a way to do it using Class.asSubclass(). After creating the instance of Foo, I need to cast it from Foo<E> to Foo<T> event though E and T will always be the same types.

I can't weaken the signature of createEnumFoo() because it is calling Enum.valueOf(enumType, ...) and requires the result of this to be of type E.

final class Example {
    <E extends Enum<E>> Foo<E> createEnumFoo(Class<E> enumType) {
        // This makes use of e.g. Enum.valueOf(enumType, ...).
        return null;
    }

    <E extends Enum<E>> Class<E> castEnumType(Class<?> enumType) {
        return (Class<E>) enumType;
    }

    <T> Foo<T> createFoo(Class<T> type) {
        if (Enum.class.isAssignableFrom(type))
            return (Foo<T>) createEnumFoo(castEnumType(type));
        else
            // Here we would do something else or maybe throw an exception.
            return null;
    }

    interface Foo<T> {
    }
}

Is there a simpler way to do this?


Some context

To clarify the problem I'm facing, I'll explain how this problem actually arose in a project I'm working on:

In the code where I came across this problem, Foo<T> is actually Converter<T>, which is an interface which allows an instance of T to be serialized and de-serialized from and to a JSON value:

public interface Converter<T> {
    JsonObject encode(T value);

    T decode(JsonObject data);
} 

And createFoo() is actually a method converterForType() which takes a Class<T> instance and dynamically dispatches to a bunch of static methods and fields that create/contain converters for common Java types and types specific to the project. Normally when a converter is needed, the appropriate method/field is accessed directly but there are some places where the type is only known at runtime, which is where converterForType() is used.

Now I wanted to extend that method to automatically handle enum types by converting those to JSON strings containing the name of the enum constant. This is why I need to call the method enumConverter() from converterForType(). This is the implementation of enumConverter():

public static <E extends Enum<E>> Converter<E> enumConverter(final Class<E> enumClass) {
    return new Converter<E>() {
        public JsonObject encode(E value) {
            return Json.convert(value.name());
        }

        public E decode(JsonObject data) {
            return Enum.valueOf(enumClass, data.asString());
        }
    };
}
like image 337
Feuermurmel Avatar asked Mar 14 '14 13:03

Feuermurmel


1 Answers

What about this, use raw types for createEnumFoo method
Edit: fixed compile error reported by @Feuermurmel in comments

@SuppressWarnings({ "unchecked", "rawtypes" })
final class Example
{
    <E extends Enum<E>> Foo<E> createEnumFoo(Class enumType)
    {
        // This makes use of e.g. Enum.valueOf(enumType, ...).
        Enum x = Enum.valueOf(enumType, "x");
        return (Foo<E>) x;
    }

    <T extends Enum> Foo<T> createFoo(Class<T> type)
    {
        if (Enum.class.isAssignableFrom(type))
            return (Foo<T>) createEnumFoo(type);
        else
            // Here we would do something else or maybe throw an exception.
            return null;
    }

    interface Foo<T>
    {
    }
}
like image 75
Ahmed Qasid Avatar answered Sep 28 '22 07:09

Ahmed Qasid