Java 未选中的重写返回类型

我有一个项目,它具有以下组件:

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()

我的问题是,这可以打破吗,有没有更好的方法来做到这一点?我可以创建一个基类,并为两者提供子类,但这似乎不是很优雅。DoThingSomeThingSomeOtherThing

编辑:我想避免使用通用。BaseThing


答案 1

让我们先看看你不想成为泛型的类:BaseThing

public abstract class BaseThing {
    public abstract <T extends BaseThing> ThingDoer<T, String> getThingDoer();
}

这不是一个泛型类,但它包含一个泛型方法。通常,像这样的泛型方法被设计为编译器基于方法的某个参数来绑定类型。例如:。但在您的情况下,您的方法不带任何参数。这也有点常见,在方法的实现返回“普遍”泛型(我的术语)的情况下,就像从实用程序类中返回此方法一样:。此方法不带任何参数,但类型将从调用上下文中推断出来;它之所以有效,只是因为 的实现返回一个在所有情况下都是类型安全的对象。由于类型擦除,该方法实际上并不知道调用时的类型。<T>public <T> Class<T> classOf(T object)Collectionspublic <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();
    }
}

在这里,您希望重写基类中的方法。只要返回类型在原始方法的上下文中仍然有效,就允许在 Java 中重写返回类型。例如,您可以重写一个方法,该方法使用始终为该方法返回的特定实现返回,因为 .abstractNumberIntegerIntegerNumber

但是,对于泛型,a 不是 .因此,虽然您的抽象方法被定义为返回(对于某些),但返回并且通常与某些未知的重载不兼容,即使并且两者都从 .List<Integer>List<Number>ThingDoer<T, String>T extends BaseThingThingDoer<SomeThing, String>ThingDoer<SomeOtherThing, String>TSomeThingSomeOtherThingBaseThing

调用方(来自抽象 API)期望一些未知的、不可执行的,不能保证由任何一个具体实现来满足。实际上,您的具体重载不再是泛型的(它们返回特定的、静态绑定的类型参数),并且与抽象类中的定义相冲突。T

编辑:定义抽象方法的“正确”方式(无警告)应该是这样的:

public abstract ThingDoer<? extends BaseThing, String> getThingDoer();

这让调用方清楚地知道,它正在将 带有第一个类型参数绑定到扩展的东西 (因此它可以像使用它一样使用它),但是当抽象 API 访问时,调用方将不知道特定的实现。ThingDoerBaseThingBaseThing

编辑#2 - 我们在聊天中讨论的结果...

OP的原始示例用法是:

BaseThing thing = /* ... */;
thing.getThingDoer().do(thing);

请注意,如何将相同的引用传递回从同一事物的方法返回的对象中的方法。返回的对象需要紧密绑定到的具体实现类型(根据OP)。对我来说,这闻起来像是破碎的封装。thinggetThingDoer()getThingDoer()thing

相反,我建议将逻辑操作作为 API 的一部分公开,并将委派封装为内部实现细节。生成的 API 将如下所示:BaseThingThingDoer

thing.doTheThing();

并实现得有点像:

public class SomeThing extends BaseThing {
    @Override public void doTheThing() {
        Things.getSomeThingDoer().do(this);
    }
}

public class SomeOtherThing extends BaseThing {
    @Override public void doTheThing() {
        Things.getSomeOtherThingDoer().do(this);
    }
}

答案 2

推荐