Java 泛型工厂方法为何不能返回满足 V 上界的实例?
错误来自返回类型 V 的含义。V extends JPanel & LabeledInput<T> 为调用时确定的类型设置了上界,无法保证方法创建的每一种面板都能赋值给 V。编译器能够识别间接继承关系,这两处报错与继承链的深度无关。
如果调用方只需要 getValue(),可以直接返回 LabeledInput<T>。如果还需要把结果作为 JPanel 使用,可以让两种实现继承一个同时具备这两种能力的公共抽象父类,再返回该父类。
为什么满足上界仍然不能赋值给 V
原方法的声明是:
public static <T, V extends JPanel & LabeledInput<T>>
V createPanel(String label, T... items)
这个声明保证,对于某次调用确定的 V,它既是 JPanel 的子类型,也实现了 LabeledInput<T>。
两个类型满足相同的上界,不代表它们可以互相赋值。例如,ItemSelectRadioPanel<String> 和 ItemSelectComboPanel<String> 都可以满足约束,却仍然是两个不同的类型。
根据原方法签名,调用方可以明确指定 V:
ItemSelectRadioPanel<String> panel =
ItemSelectPanelFactory
.<String, ItemSelectRadioPanel<String>>createPanel(
"选择", "A", "B", "C", "D", "E"
);
这里指定的返回类型是 ItemSelectRadioPanel<String>,但五个选项会使方法进入创建 ItemSelectComboPanel<String> 的分支。方法无法保证返回签名所承诺的类型,因此编译器在方法体内就拒绝了赋值。
即使没有调用方这样使用,泛型方法的实现也必须对所有满足约束的类型实参成立。
只需要读取选中值时,返回公共接口
如果调用方只调用 getValue(),可以去掉类型参数 V。前提是两种面板都实现了 LabeledInput<T>,包括通过父类间接实现的情况。
public interface ItemSelectPanelFactory {
int MAX_RADIO_COUNT = 4;
@SafeVarargs
public static <T> LabeledInput<T> createPanel(
String label, T... items) {
if (MAX_RADIO_COUNT < items.length) {
return new ItemSelectComboPanel<T>(label, items);
}
return new ItemSelectRadioPanel<T>(label, items);
}
}
调用方式如下:
LabeledInput<String> input =
ItemSelectPanelFactory.createPanel("选择", "A", "B");
String value = input.getValue();
这种写法表达了工厂能够提供的公共接口。返回值的静态类型是 LabeledInput<String>,因此不能直接用于要求 JPanel 或其他 Swing 组件类型的参数位置。
同时需要 JPanel 和 getValue() 时,引入公共抽象父类
可以声明一个公共父类,让它同时继承 JPanel 并实现 LabeledInput<T>:
public abstract class ItemSelectPanel<T>
extends JPanel implements LabeledInput<T> {
}
然后调整继承关系:
ItemSelectRadioPanel<T>改为继承ItemSelectPanel<T>,继续实现ActionListener。- 如果
AbstractLabeledInputComponent原本直接继承JPanel,让它改为继承ItemSelectPanel<T>。如果中间还有其他父类,需要在合适的层级接入这个公共父类,并保留原有泛型约束和构造逻辑。 ItemSelectComboPanel<T>保留其所需的组件继承关系,但必须最终成为ItemSelectPanel<T>的子类型。
完成这些调整后,工厂方法可以返回公共父类:
@SafeVarargs
public static <T> ItemSelectPanel<T> createPanel(
String label, T... items) {
if (MAX_RADIO_COUNT < items.length) {
return new ItemSelectComboPanel<T>(label, items);
}
return new ItemSelectRadioPanel<T>(label, items);
}
调用方就可以同时使用两种能力:
ItemSelectPanel<String> panel =
ItemSelectPanelFactory.createPanel("选择", "A", "B");
container.add(panel);
String value = panel.getValue();
这里假定 container 是可添加 Swing 组件的容器。该方案需要调整类层次,仅修改工厂方法的返回类型还不够。
不要用强制转换掩盖返回类型错误
将赋值改为下面这样,并没有修复类型契约:
result = (V) new ItemSelectComboPanel<T>(label, items);
这种转换会产生 unchecked 警告。当调用方要求另一种具体面板类型时,还可能在运行时出现 ClassCastException。
@SafeVarargs 也不能解决这个问题。它针对泛型可变参数的安全性声明,与对象能否转换为 V 是两回事。保留该注解前,还需要检查方法及接收 items 的构造器是否安全使用数组;仅凭现有代码无法确认这一点。
对于按选项数量选择实现的工厂,可以返回公共接口或公共父类,表达两种实现共同提供的能力。工厂无法保证返回调用方指定的具体子类型。
备注:内容仅供参考。
