如何使用编译时genericsvalidation替换运行时instanceof检查

真正的Java Generics专家有一点难题……;)

假设我有以下两个接口:

interface Processor { void process(Foo foo); } interface Foo { Processor getProcessor(); } 

例如,以下两个实现类:

 static class SomeProcessor implements Processor { static final SomeProcessor INSTANCE = new SomeProcessor(); @Override public void process(Foo foo) { if (foo instanceof SomeFoo) { // <-- GET RID OF THIS ? // process ((SomeFoo) foo) } } } class SomeFoo implements Foo { @Override public Processor getProcessor() { return SomeProcessor.INSTANCE; } } 

是否有一些方法可以使这两个接口通用,这样我就不需要在process()函数中使用标记的instanceof检查,并且仍然在我的代码中的其他地方使用以下构造?

 foo.getProcessor().process(foo); 

(当然,我不知道我正在处理的Foo的子类)

换句话说:我正在寻找一种在对象中定义函数的方法,这样它只能返回另一个处理包含该函数的对象类型的对象。 注意:我不只是讨论处理包含函数的对象的最小公分母超类(上图:Foo),而是该对象的实际类(上图:SomeFoo)。

(除非我现在真的很愚蠢,否则这听起来就像听起来一样微不足道……)

这比我想象的要糟糕。 我的看法:

 interface Processor> { void process(F foo); } interface Foo> { Processor getProcessor(); } interface SomeFoo extends Foo { @Override SomeProcessor getProcessor(); } interface SomeProcessor extends Processor { @Override void process(SomeFoo foo); } 

现在,以下将编译:

 > void process(F foo) { foo.getProcessor().process(foo); } 

 void process(Foo foo) { foo.getProcessor().process(foo); } 

不会,因为编译器不能知道传递的foo的实际类型是其类型参数的子类型,因为有人可以写:

  class Bar implements Foo { ... } 

我们可以通过要求foo的子类型实现到其类型参数的转换来解决这个问题:

 abstract class Foo> { abstract Processor getProcessor(); abstract F getThis(); } class SomeFoo extends Foo { @Override SomeFoo getThis() { return this; } @Override Processor getProcessor() { return new SomeProcessor(); } } 

现在,我们可以写:

 > void process(Foo foo) { foo.getProcessor().process(foo.getThis()); } 

并调用它

 Foo foo = ...; process(foo); 

为了便于使用,我建议将辅助方法移动到类Foo中:

 abstract class Foo> { abstract Processor getProcessor(); abstract F getThis(); void processWith(Processor p) { p.process(getThis()); } } 

更新:我认为newaccts更新的答案显示了一个更优雅的解决方案,因为它不需要递归类型边界。

从类型安全的角度来看,所有递归边界的东西都是不必要的。 就像这样就足够了:

 interface Processor { void process(F foo); } interface Foo { Processor getProcessor(); } class SomeFoo implements Foo { @Override SomeProcessor getProcessor() { ... } } class SomeProcessor implements Processor { @Override void process(SomeFoo foo) { ... } } // in some other class: > void process(F foo) { foo.getProcessor().process(foo); } 

换句话说:我正在寻找一种在对象中定义函数的方法,这样它只能返回另一个处理包含该函数的对象类型的对象。 注意:我不只是讨论处理包含函数的对象的最小公分母超类(上图:Foo),而是该对象的实际类(上图:SomeFoo)。

这不可能在Java中声明。 相反,如上所示,您可以在类外部使用generics方法(或者是该类的静态方法),它同时接受对象和处理器,并在两者上强制执行类型。


更新:如果你想能够用任何Foo调用process ,那么我们也可以将meriton的getThis()思想集成到这个代码中(同样,没有递归边界):

 interface Foo { Processor getProcessor(); F getThis(); } class SomeFoo implements Foo { SomeProcessor getProcessor() { ... } SomeFoo getThis() { return this; } } // in some other class:  void process(Foo foo) { foo.getProcessor().process(foo.getThis()); } 

你可以用generics做的是:

 interface Processor { void process(T foo); } interface Foo { Processor getProcessor(); } 

然后

 class SomeProcessor implements Processor { public void process(SomeFoo foo) { // TODO } } class SomeFoo implements Foo { public Processor getProcessor() { return processor; } } 

这显然意味着您需要一个带有通配符的对应Foo.setProcessor() ,这反过来意味着您最终会在某处获得未经检查的强制转换。 从语言的角度来看,这是不安全的,没有办法解决这个问题。

您可以使用超类型令牌检查处理器实例化,但这将在运行时发生,因此在编译时您无法保证API被滥用。 只需记录下它就能做到最好。

这个馅饼说明了我的想法。 您无法将此问题建模为在编译时具有类型安全性,因为Foo接口需要一种方法来声明方法返回使用当前接口实现实例化的generics类型。 这会破坏inheritance,不能用Java完成。

但是对于超类型令牌,您可以在运行时检查处理器是否是正确的类型,如果您在API文档中明确指出只有处理器有权在Foo实例上调用setProcessor() ,则始终在语言级别保证。 如果客户端程序员违反并使用不正确的类型调用setProcessor() ,则您的类仍将抛出exception,但在运行时。

附录:为什么你不应该参数化Foo

我想添加一个小段来解释为什么我不喜欢meriton的答案(当前接受的答案),以及所有其他涉及参数化Foo类型的答案,以便它变成Foo

软件失败:这不是一件坏事或不寻常事,只是发生了。 它越早越好,所以我们可以修复它并避免损失(通常是金钱,但实际上是有人可能关心的)。 这是今天选择Java的一个令人信服的理由:由于在编译时检查了类型,因此一大类错误永远不会产生。

这是generics进入的地方:另外一整类错误被遗漏了。 回到你的情况,有人建议在Foo添加一个类型参数,这样你就可以在编译时获得类型安全性。 但是,使用meriton的实现,可以编写绕过编译器检查的不合逻辑的代码:

 class SomeFoo implements Foo {} 

有人可能会说,判断程序在语义上是否正确并不是编译器的工作 – 我同意 – 但是一个好的语言可以让编译器发现问题并提示解决方案。 在这里使用类型参数只是大喊一个明智的软件。

您最好坐下来,关闭IDE并思考如何改进您的设计,以便客户端代码不会失败,而不是依赖于脆弱的约定。 最终,这是软件失败的主要原因:不是因为语言是强类型还是动态类型,而是因为开发人员误解了API。 使用强类型语言是一种防止一种类型的误用的方法。

您的界面非常奇怪,因为它定义了一个getProcessor()但没有说明谁负责设置处理器。 如果它是提供自己的处理器的Foo ,那么类型安全只会被一个非常愚蠢的开发人员打破; 但由于它可以在运行时检查(参考我的带有超类型令牌的演示),因此可以通过良好的开发过程(unit testing)轻松保证。 如果定义setProcessor()或等效项,结论不会改变太多。

您正在寻找的API无法用Java描述 – 顺便说一下,我认为对于每一种面向对象的语言都是如此,因为它打破了inheritance的主要规则,父类不知道他们的孩子(这反过来带来多态性)。 使用通配符(正如我在演示中暗示的那样)是您可以使用Javagenerics的最接近的,提供类型安全性并且易于理解。

我鼓励您只在处理器中添加一个类型参数,并为您的代码编写好的文档和unit testing,而不是强迫Java规则以类型安全的名义在这里真的不买任何东西。

不,这不是Java Generics的设计目标。

此外,在我看来,对类型检查的需要表明设计存在问题。 我建议尝试重新设计它,这样就没有必要了。

 interface Processor> { void process(F fooSubclass); } interface Foo> { Processor getProcessor(); } 

我没有测试过这是完全正确的,但这种具有generics类型的模式本身可能与编译时Javagenerics相近。

这里的代码完全“通用化”,但稍有改动: INSTANCE变量不是静态的。

 interface Processor> { void process(T foo); } interface Foo> { Processor getProcessor(); } static class SomeProcessor> implements Processor { final SomeProcessor INSTANCE = new SomeProcessor(); @Override public void process(T foo) { // it will only ever be a SomeFoo if T is SomeFoo } } class SomeFoo implements Foo { @Override public Processor getProcessor() { return new SomeProcessor().INSTANCE; } } 

没有编译器错误或警告。

INSTANCE是一个实例变量,因为类类型没有通过静态任何东西。 如果你真的只想要一个INSTANCE ,那就在课堂上使用单例模式。