Java泛型方法中的有界参数

10
我用泛型方法测试了一些有界参数的内容,发现了一些奇怪的行为。如果有人能解释下面代码片段中的两个错误就太好了。
假设有两个类Class1和Class2都继承自BaseClass。Class2实现了一个接口。
在Class1中,我有一个方法以以下方式返回Class2的实例:
public class Class2 extends BaseClass implements Interface {

    @Override
    public void method() {
        System.out.println("test"); //$NON-NLS-1$
    }
}

public class Class1 extends BaseClass {

    public <T extends BaseClass & Interface> T getTwo() {
        return new Class2();
        // Error: Type mismatch: cannot convert from Class2 to T
    }

    public static void main(String[] args) {
        Interface two = new Class1().getTwo();
        // Error: Bound mismatch: The generic method getTwo() of type Class1 is
        // not applicable for the arguments (). The inferred type Interface is
        // not a valid substitute for the bounded parameter <T extends BaseClass
        // & Interface>
        System.out.println(two);
    }
}

看起来是泛型的误用。实际上,getTwo 方法的返回类型必须是 Class2BaseClass 或者 Interface。如果 T 只在一个方法中定义,编译器就无法确定哪个类在行 new Class1().getTwo() 中替换了 T。除非 getTwo 方法具有这种类型的输入参数,否则不可能做到这一点。 - DRCB
2个回答

5

第一个编译错误是因为方法声明的类型参数由调用者指定,而不是方法实现。也就是说,给定

class Class3 extends BaseClass implements Interface { ... }

调用者可以编写

Class3 c3 = new Class1().<Class3>getTwo();

但是方法的实现返回一个Class2,它不是T = Class3的子类型,因此第一个编译错误发生。

第二个编译错误发生的原因是调用者没有明确指定的类型参数是从方法参数和分配方法返回值的变量的类型推断出来的。这里推断失败了。Java语言规范建议的常见解决方法是在这种情况下明确指定类型参数(类型推断旨在方便处理简单情况;它并不旨在覆盖所有情况)。

至于如何正确声明这个类型参数,我需要知道您想通过这些声明实现什么目的。


1
+1 很好的回答。我认为在你的第二个代码示例中,语法应该是 new Class1().<Class3>getTwo() - Paul Bellora
我理解第一个编译错误的原因,但对于第二个错误还不太确定。 在我的系统上,调用BaseClass two = new Class1().getTwo();似乎没有问题,但在同事的系统上失败了。而且,调用Interface two = new Class1().getTwo();在我的系统上也失败了(如上所示)... - Marco
1
你试过像我的Class3示例一样明确指定类型参数吗?或者通过声明Class2 two = new Class1().getTwo();来调整类型推断?我无法立即解释为什么它在你的系统上会有不同的行为。你是否尝试使用相同的编译器和相同的设置运行完全相同的示例程序? - meriton
假设“getTwo()”方法是接口的一部分,您不知道getTwo的确切返回类型,您想以通用方式使用返回的对象。 您知道为什么'BaseClass two = class1.getTwo();'可以正常工作,而'Interface two = class1.getTwo();'无法编译吗? - Marco
1
好的,我可以想象。我可能会定义abstract class BaseClassWithInterface extends BaseClass implements Interface {}并要求Class2扩展它。然后任何调用者都可以使用BaseClassWithInterface变量来存储对象。至于你为什么的问题:可能是因为Java语言规范中第15.12.2.7节指定的算法如此规定。 - meriton
啊,好的,这就是类型推断的工作方式了吧?我可以按照你描述的那样定义一个BaseClassWithInterface,也可以定义一个带有泛型参数的接口。我觉得我对Java中的泛型有了更深入的理解。谢谢Marco。 - Marco

4

为什么在方法 getTwo 中使用泛型,当你知道它是一个 Class2?只需要这样做:

public Class2 getTwo() {
    return new Class2();
}

如果你重写了一个方法 public <T extends BaseClass & Interface> T getTwo(),编译器会允许你声明你的实现为 public Class2 getTwo(),当你的 TClass2 时。

不要急于覆盖。在您的情况下,您将覆盖一个可以返回满足约束条件的任何T的方法,而用返回Class2的方法来替换它是不允许的,原因与OP的代码被禁止的原因相同。我不确定Java甚至能够表达带有存在量词的返回类型。 - Judge Mental

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接