Gili
Gili

Reputation: 90150

Why does TypeToken fail to capture generic type if factory method is used?

Following up on How to use TypeToken to get type parameter?, it seems that if I instantiate a class using a factory method then TypeToken is no longer able to capture generic type parameters.

import com.google.common.reflect.TypeToken;

public class Test<E extends Enum<E>> {

    private static enum MyEnum {
        FIRST,
        SECOND
    };

    private final TypeToken<E> enumType = new TypeToken<E>(getClass()) {
    };

    public static void main(String[] args) {
        Test<MyEnum> container = new Test<MyEnum>() {
        };
        System.out.println("constructor: " + container.enumType.getRawType());
        System.out.println("factory    : " + build(MyEnum.class).enumType.getRawType());
    }

    public static <E extends Enum<E>> Test<E> build(Class<E> type) {
        return new Test<E>() {
        };
    }
}

The above example outputs:

constructor: class Test$MyEnum
factory    : class java.lang.Enum

Why doesn't this work and can it be fixed?

Upvotes: 1

Views: 461

Answers (2)

k5_
k5_

Reputation: 5568

Depending on how the generated type should be used, another option is actually implement a ParameterizedType. Thats basically what the TypeToken.getType() would return.

    public static ParameterizedType parameterizedType(Class<?> rawType, Type... actualTypeArguments) {
    return new ParameterizedType() {
        @Override
        public Type[] getActualTypeArguments() {
            return actualTypeArguments;
        }

        @Override
        public Class<?> getRawType() {
            return rawType;
        }

        @Override
        public Type getOwnerType() {
            return null;
        }
    };
}

Upvotes: 0

Gili
Gili

Reputation: 90150

Seeing as no one bothered converting their comment into a formal answer, I'll go ahead and do so:

As previously discussed, Java only retains information about type-parameters of generic superclasses. Since the type-parameter is associated with a method (as opposed to a superclass) the runtime value of <E> is not retained. Replacing the factory method with

public static Test<MyEnum> of() {
    return new Test<MyEnum>() {
    };
}

will result in the right value, but obviously this defeats the purpose of the factory method because we're forced to use a hard-coded enum type.

To recap, TypeToken won't work here. The only way to retain information about the type-parameter is to pass a Class<E> as follows:

public class Test<E extends Enum<E>> {

    private static enum MyEnum {
        FIRST,
        SECOND
    };

    private final Class<E> type;

    public Test(Class<E> type) {
        this.type = type;
    }

    public static void main(String[] args) {
        Test<MyEnum> container = new Test<>(MyEnum.class);
        System.out.println(container.type);
        System.out.println(of(MyEnum.class).type);
    }

    public static <E extends Enum<E>> Test<E> of(Class<E> type) {
        return new Test<>(type);
    }
}

Upvotes: 1

Related Questions