6

我使用 Java 8。在我的设计中,有一些简单的类对值参数进行建模,例如FloatParameterEnumParameter<E>。A 具有这些类的通用通用超类 ( GenericParameter<T>),它实现了参数名称及其默认值。子类实现了特定于它们的其他属性,例如FloatParameter.

此外,我想使用参数的类型,而不管它们的具体类型如何。但我仍然想以它们是GenericParameter<T>. 为了做到这一点,我创建了一个方法,例如process(Class<? extends GenericParameter<?>> paramType).

现在,问题是EnumParameter.class 不能分配给类型变量,Class<? extends GenericParameter<?>>FloatParameter.class 可以

此外,我列出了类的代码以使其更加清晰和可重复:

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

最后,当使用非泛型基类时,没有问题:

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

请问,您有什么建议吗?

我可以process(Class<?> paramType)作为一种解决方法或进行强制转换,但我想从编译器的静态类型检查中受益。

编辑:

在注册为每种参数类型生成 GUI 组件的工厂时,我想使用强制转换。代码如下所示:

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

在这种情况下,编译器会在编译时检查添加的参数类型。此外,代码将更加不言自明。

编辑2:

目前,我正在使用建议的方法来为该方法引入类型参数addParameterComponentFactory。签名如下所示:

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

通过这个定义,我可以指定TypedParameter.classEnumParameter.class- 也是一个类型参数)以及获得静态类型检查。

4

1 回答 1

2

让我们从 API 的核心部分开始。您有一个泛型Parameter<T> 类型,它表示一些具有 type 值的命名参数T。您拥有专门用于编辑或显示特定类型参数的 GUI 组件,并且您希望能够注册工厂来创建这些组件。

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> {}

如果我对您的理解正确,那么您在弄清楚如何声明一个静态强制某种 Parameter类型与专门用于该类型的 GUI 组件的工厂之间关系的方法时会遇到麻烦。例如,您希望能够这样写:

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

该问题与泛型子类型的规则有关,您可以通过使用类型变量而不是嵌入式通配符来解决它们。这应该给你你想要的类型检查,而不需要讨厌的强制转换:

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

解释[1]

P extends Parameter<?>解释引入in 中使用的新类型Class<P>和直接陈述之间的区别Class<? extends Parameter<?>>

这很复杂,所以请耐心等待。让我们谈谈通配符、原始类型和转换。考虑以下:

// 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;

场景 1(a) 和 1(b) 都出于相同的原因进行编译:因为原始类型 G可能会经历未经检查的转换 为表单的任何参数化类型G<T_1, ..., T_n>

方案 2无法编译。但为什么?

在场景 2 中,第二个分配中的任何一方都不是原始类型。为使 赋值有效,必须进行恒等转换或 从右手类型到左手类型的扩大转换。为了扩大引用类型的转换,左侧类型必须是右侧类型的超类型。当这些类型是泛型时,将应用泛型子类型的规则 。具体来说,左侧的类型参数必须 包含 右侧的类型参数。

Class<String>从to的分配Class<? extends Object>是有效的。 Class<String>Class<? extends Object>因为 ? extends Object contains String的泛型子类型。在场景 2 中,要使第二个分配有效,GenericParameter<?>必须包含 GenericParameter,但事实并非如此。 T不是; 子类型 是 的超类型。因此,根据通用子类型规则, 不是 的子类型,并且赋值无效。T<?>TT<?>Class<T>Class<T<?>>

那么为什么以下工作?

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

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

在上面的调用中,类型推断P完全由 Class<P>参数驱动。您正在传递 a Class<EnumParameter>,因此P 在这种情况下被绑定到原始类型EnumParameter。为了满足约束 P extends GenericParameter<?>GenericParameter<?> 必须可以从 分配EnumParameter,并且可以通过未经检查的转换分配,就像在场景 1(a) 和 1(b) 中一样。

[1]这种解释是公然的抄袭 ,是其他优秀 Stack Overflow 答案的合并,主要来自 radiodef

于 2017-09-20T01:34:39.840 回答