我有一个包含以下组件的项目:
public abstract class BaseThing {
public abstract <T extends BaseThing> ThingDoer<T, String> getThingDoer();
}
public class SomeThing extends BaseThing {
public ThingDoer<SomeThing, String> getThingDoer() {
return Things.getSomeThingDoer();
}
}
public class SomeOtherThing extends BaseThing {
public ThingDoer<SomeOtherThing, String> getThingDoer() {
return Things.getSomeOtherThingDoer();
}
}
public class Things {
public ThingDoer<SomeThing, String> getSomeThingDoer {
return getThingDoer(SomeThing.class);
}
public ThingDoer<SomeOtherThing, String> getSomeOtherThingDoer {
return getThingDoer(SomeOtherThing.class);
}
private <D extends ThingDoer<T, String> D getThingDoer(Class<T> clazz) {
//get ThingDoer
}
}
public class ThingDoer<T, V> {
public void do(T thing) {
//do thing
}
}
public class DoThing {
private BaseThing thing;
public void doIt() {
thing.getThingDoer().do(thing);
}
}
我在SomeThing.getThingDoer()
中收到一条编译器警告:
未选中覆盖:返回类型需要取消选中转换。
找到
ThingDoer<SomeThing, String>
,必填ThingDoer<T, String>
Everthing编译得很好,虽然我还没有机会测试DoThing.doIt()
,但我没有理由相信它不起作用。
我的问题是,这可以打破并且有更好的方法吗?我可以使DoThing
成为基类,并为SomeThing
和SomeOtherThing
提供子类,但这看起来并不优雅。
编辑:我希望避免使BaseThing
通用。
答案 0 :(得分:9)
让我们首先看一下您不希望通用的BaseThing
课程:
public abstract class BaseThing {
public abstract <T extends BaseThing> ThingDoer<T, String> getThingDoer();
}
这是不通用类,但它包含泛型方法。通常,设计这样的通用方法,以便类型<T>
基于方法的某个参数由编译器绑定。例如:public <T> Class<T> classOf(T object)
。但在你的情况下,你的方法不需要参数。如果方法的实现从Collections
实用程序类public <T> List<T> emptyList()
返回类似于此方法的“通用”泛型(我的术语),那么这也有点常见。此方法不带参数,但类型<T>
将从调用上下文推断;它只能 ,因为emptyList()
的实现会返回一个在所有情况下都是类型安全的对象。由于类型擦除,该方法在调用时实际上并不知道T
的类型。
现在,回到你的班级。当您创建BaseThing
的这些子类:
public class SomeThing extends BaseThing {
public ThingDoer<SomeThing, String> getThingDoer() {
return Things.getSomeThingDoer();
}
}
public class SomeOtherThing extends BaseThing {
public ThingDoer<SomeOtherThing, String> getThingDoer() {
return Things.getSomeOtherThingDoer();
}
}
在这里,您希望覆盖基类中的abstract
方法。只要返回类型在原始方法的上下文中仍然有效,就允许在Java 中覆盖返回类型。例如,您可以覆盖返回Number
的方法,该方法具有始终为该方法返回Integer
的特定实现,因为Integer
是 Number
但是,对于泛型,List<Integer>
不是 a List<Number>
。因此,虽然您的抽象方法被定义为返回ThingDoer<T, String>
(对于某些T extends BaseThing
),但返回ThingDoer<SomeThing, String>
和ThingDoer<SomeOtherThing, String>
的重载通常与某些未知的{{1}不兼容即使T
和SomeThing
都来自SomeOtherThing
。
调用者(来自抽象API)期望某些未知的,不可执行的BaseThing
无法保证满足您的任何具体实现。实际上,您的具体重载不再是通用的(它们返回特定的,静态绑定的类型参数),并且与抽象类中的定义冲突。
修改强>: 定义抽象方法的“正确”方式(无警告)应该是这样的:
T
这使得调用者明白它正在获得public abstract ThingDoer<? extends BaseThing, String> getThingDoer();
,其第一个类型参数绑定到扩展ThingDoer
的某些(因此它可以像使用它一样使用它是一个BaseThing
),但调用者将不会知道抽象API访问时的具体实现。
编辑#2 - 我们在聊天中讨论的结果...
OP的原始示例用法是:
BaseThing
注意如何将相同的BaseThing thing = /* ... */;
thing.getThingDoer().do(thing);
引用传递回从同一事物的thing
方法返回的对象中的方法。 getThingDoer()
返回的对象需要与getThingDoer()
的具体实现类型紧密绑定(根据OP)。对我而言,这就像破碎的封装一样。
相反,我建议将逻辑操作公开为thing
API的一部分,并将委托作为内部实现细节封装到BaseThing
。生成的API看起来像:
ThingDoer
并且有点像:
thing.doTheThing();