假设我有一个像这样的界面

public interface Foo<Ret, Arg> {
    public Ret doSomething(Arg arg);
}

还有一个仅具有包可见性的具体实现类。
class FooImpl<Ret, Arg1, Arg2> implements Foo<Ret, Arg>, Bar<Ret, Arg1, Arg2> {
    // This class also implements something else making use of Arg2, so you cannot safely remove it
    // But nothing relating to Arg2 is needed to create a FooImpl
}

(如何发生这种情况的一个示例是在Foo中实现BarFooImpl将两个接口声明的方法转发给一个varargs方法)

现在,假设在同一包中的某处有一个静态方法,该方法返回FooImpl作为Foo。有人认为您可以使用return new FooImpl<Ret, Arg1, ?>(...),而实际上却不能(您必须使用具体的虚拟类型作为Arg2,即return new FooImpl<Ret, Arg1, Object>(...))。

知道为什么会这样吗,特别是因为Foo接口和程序包可见性有效地将FooImpl隐藏在使用静态方法的任何地方?是否因为这样一个事实,即在需要具体类型的地方,仍然可以在某种程度上使用反射来到达BarFooImpl部分?

最佳答案

Java编译器尽力不变得聪明。在很多情况下,很明显不需要某些信息即可生成正确的代码,但仍然会抱怨。发明Java时,无论C多么危险或愚蠢,世界都以C的草率态度来尝试编译所有内容。 javac的目标是确保人们不能shoot themselves into the foot easily

因此,它假定您提到Arg2是有原因的-如果不需要某些内容,那么您肯定不会将其放入代码中。

解决方案:

  • 实现Bar时分配一种类型:class FooImpl<Ret, Arg1> implements Foo<Ret, Arg>, Bar<Ret, Arg1, Object>
  • 创建一个BetterFooImpl<Ret, Arg1> extends FooImpl<Ret, Arg1, Object>。这对API的使用者隐藏了第三种类型的参数。
  • 尝试摆脱Bar的第三个参数
  • 谨慎使用泛型。这可能令人惊讶,但是在某些情况下,泛型根本无法正常工作。这通常是由于细微的实现细节导致@SuppressWarning注释激增。发生这种情况时,我会询问SO,而当我找不到我能理解的解决方案时,便会删除泛型。编写可维护的代码比通过泛型迷宫找到巧妙的方法更为重要。
  • 09-05 09:27