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

1

6

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.

Grievance answered 19/9, 2017 at 10:11 Comment(2)
Please can you post some code where you need to cast TypedGenericParameter.class to Class<? extends GenericParameter<?>>?Railroad
Hi, I tried to add an example.Torgerson
J
2

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.

Jovita answered 20/9, 2017 at 1:34 Comment(6)
Wow, the addition of the type parameter to the addComponentFactory solved the problem. But, could you please explain the difference between introducing a new type parameter P extends Parameter<?> used in Class<P> and stating directly Class<? extends Parameter<?>>?Torgerson
I didn’t use Parameter<?> as my upper bound; I used Parameter. So there are really two questions: (1) why use a raw type in the upper bound, and (2) why use a generic parameter instead of wildcards? I will attempt to answer both once I have had breakfast and some caffeine :).Jovita
Sure. I was aware of raw Parameter, but I used Parameter<?> as the upper bound and it works nicely since the compiler does not complain about rawtype. I could comment on that, sorry.Torgerson
And that worked? Would you mind showing me the signature for your add method, and the declaration of your factory class? (Just append to the bottom of your question.)Jovita
Sure, I added it as EDIT 2.Torgerson
Sorry for the delay. Even after years of writing Java code, and even writing a Java decompiler, I still have trouble grasping some of the nuances of generics, especially where wildcards are concerned. Hopefully my addendum is both useful and correct.Jovita

© 2022 - 2024 — McMap. All rights reserved.