Java未选中覆盖返回类型

时间:2014-05-14 04:40:23

标签: java generics polymorphism abstract-class return-type

我有一个包含以下组件的项目:

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成为基类,并为SomeThingSomeOtherThing提供子类,但这看起来并不优雅。

编辑:我希望避免使BaseThing通用。

1 个答案:

答案 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}不兼容即使TSomeThing都来自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();