Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Class of a generic Java type cannot be assigned to a variable whose Class type is upper bounded by a generic super type

I use Java 8. In my design a have a few simple classes which model value parameters such as FloatParameter or EnumParameter<E>. A have a common generic super class of these classes (GenericParameter<T>) which implements parameter name and its default value. The sub classes implement other attributes specific to them such as range in case of FloatParameter.

Moreover, I want to work with the types of the parameters regardless of their specific type. But I still want to bound the types in the way that they are sub types of GenericParameter<T>. In order to do that, I created a method such as process(Class<? extends GenericParameter<?>> paramType).

Now, the problem is that the EnumParameter.class cannot be assigned to a variable of type Class<? extends GenericParameter<?>> while FloatParameter.class can be.

Further I list the code for the classes to make it more clear and reproducible:

public class GenericParameter<T> {
    protected String name;
    protected T defaultValue;
}

public class FloatGenericParameter extends GenericParameter<Float> {
    ...
}

public class TypedGenericParameter<T> extends GenericParameter<T> {
    ...
}

Class<? extends GenericParameter<?>> fgpc = FloatGenericParameter.class; // ok
Class<? extends GenericParameter<?>> tgpc = TypedGenericParameter.class; // error: incompatible types: Class<TypedGenericParameter> cannot be converted to Class<? extends GenericParameter<?>>
Class<? extends GenericParameter> tgpc2 = TypedGenericParameter.class; // warning: [rawtypes] found raw type: GenericParameter

Finally, when using a non-generic base class, there is no problem:

public class Parameter {
    ....
}

public class FloatParameter extends Parameter {
    ...
}

public class TypedParameter<T> extends Parameter {
    ...
}

Class<? extends Parameter> fpc = FloatParameter.class; // ok
Class<? extends Parameter> tpc = TypedParameter.class; // ok

Please, do you have any suggestions?

I can go with process(Class<?> paramType) as a workaround or do casts, but I wanted to benefit from the static type checking by the compiler.

EDIT:

I would like to use the cast when registering factories that produce GUI components for each parameter type. The code looks like:

addParameterComponentFactory(EnumParameter.class, new ParameterComponentFactory() { ... })

In such case, the compiler would check the added parameter type at compile time. Also the code would be more self-explaining.

EDIT 2:

Currently, I am using the suggested approach to introduce a type parameter for the addParameterComponentFactory method. The signature looks like this:

public static <P extends GenericParameter<?>> addParameterComponentFactory(Class<P> clazz, ParameterComponentFactory pcf)

With this definition I am able to specify TypedParameter.class (EnumParameter.class - also one type param) as well as I obtain the static type checking.

like image 582
Adam Jurčík Avatar asked Sep 19 '17 10:09

Adam Jurčík


1 Answers

Let's start with the core bits of your API. You have a generic Parameter<T> type that represents some named parameter with a value of type T. You have specialized GUI components designed to edit or display specific types of parameters, and you want to be able to register factories to create these components.

class Parameter<T> {
    String name;
    T defaultValue;
}

class ParameterComponent<P extends Parameter> {
    void setParameter(final P p) {}
}

interface ParameterComponentFactory<P extends Parameter> {
    ParameterComponent<P> newComponent();
}

class FloatParameter extends Parameter<Float> {}
class FloatParameterComponent extends ParameterComponent<FloatParameter> {}

class EnumParameter extends Parameter<Enum> {}
class EnumParameterComponent extends ParameterComponent<EnumParameter> {}

If I understand you correctly, you're running into trouble figuring out how to declare a method that statically enforces a relationship between some Parameter type and a factory for GUI components specialized for that type. For example, you want to be able to write this:

addComponentFactory(EnumParameter.class, EnumParameterComponent::new);    // OK
addComponentFactory(FloatParameter.class, FloatParameterComponent::new);  // OK
addComponentFactory(FloatParameter.class, EnumParameterComponent::new);   // ERROR!

The problem is related to the rules of generic subtyping, and you can work around them by employing a type variable instead of an embedded wildcard. This should give you the type checking you want, without the need for nasty casting:

static <P extends Parameter> void addComponentFactory(
    final Class<P> parameterType,
    final ParameterComponentFactory<? extends P> factory) { ... }

Explanation[1]

Explain the difference between introducing a new type P extends Parameter<?> used in Class<P> and stating directly Class<? extends Parameter<?>>

This is complicated, so bear with me. Let's talk a bit about wildcards, raw types, and conversions. Consider the following:

// Scenario 1(a)
GenericParameter raw = /* some value */;
GenericParameter<?> wc = raw;

// Scenario 1(b)
Class raw = GenericParameter.class; 
Class<?> wc = raw;

// Scenario 2
Class<GenericParameter> classOfRaw = GenericParameter.class; 
Class<GenericParameter<?>> classOfWC = classOfRaw;

Scenarios 1(a) and 1(b) both compile for the same reason: because a raw type G may undergo an unchecked conversion to any parameterized type of the form G<T_1, ..., T_n>.

Scenario 2 does NOT compile. But why?

In Scenario 2, neither side in the second assignment is a raw type. For the assignment to be valid, there must be either an identity conversion or a widening conversion from the right-hand type to the left-hand type. For widening conversions on reference types, the left-hand type must be a supertype of the right-hand type. When these types are generic, the rules of generic subtyping apply. Specifically, the type arguments on the left-hand side must contain the type arguments on the right-hand side.

An assignment from Class<String> to Class<? extends Object> is valid. Class<String> is a generic subtype of Class<? extends Object> because ? extends Object contains String. In Scenario 2, for the second assignment to be valid, GenericParameter<?> would have to contain GenericParameter, but it doesn't. T is not a subtype of T<?>; T is a supertype of T<?>. Thus, by the generic subtyping rules, Class<T> is not a subtype of Class<T<?>>, and the assignment is not valid.

So why does the following work?

public static <P extends GenericParameter<?>> addParameterComponentFactory(
    Class<P> clazz, 
    ParameterComponentFactory pcf)

addParameterComponentFactory(EnumParameter.class, new ParameterComponentFactory() {})

In the call above, type inference on P is driven entirely by the Class<P> argument. You are passing a Class<EnumParameter>, so P in this case gets bound to the raw type EnumParameter. For the constraint P extends GenericParameter<?> to be satisfied, GenericParameter<?> must be assignable from EnumParameter, and it is assignable via an unchecked conversion, just like in Scenarios 1(a) and 1(b).

[1] This explanation is blatant plagarism an amalgamation of other excellent Stack Overflow answers, mostly by radiodef.

like image 97
Mike Strobel Avatar answered Oct 07 '22 06:10

Mike Strobel