Java泛型返回类型问题



我有以下方法:

public <T extends Result> T execute(Command<T> command)
{
return new LoginResult();
}

这里,Result是一个接口,类LoginResult确实实现了这个接口。然而,我得到了错误:

不兼容类型,必需:T,找到:com.foo.LoginResult

然而,如果我将方法签名更改为:

public Result execute(Command<T> command)

然后,相同的回流管工作正常,没有任何错误。

这里有什么问题?如何从该方法返回LoginResult

编辑:我想使用泛型的原因是,这样我就可以做以下事情:

Command<LoginResult> login = new Command<>();
LoginResult result = execute( login );

您不能执行此操作,因为编译器无法确认LoginResult的类型为T,因为它是在调用站点推断的(即调用者决定将使用哪种类型的参数)。

要回答您编辑的问题,

如果没有明确的演员阵容,就无法做到这一点。因此,最简单(但残酷)的解决方案是:

public <T extends Result> T execute(Command<T> command) {
return (T) new LoginResult();
}

但是,通过这种方式,您将承担为正确的命令实例化正确结果的全部责任,因为编译器将不再帮助您。

唯一可以帮助您动态实例化事物的是对实际Class<T>的引用。

因此,如果您在命令中添加Class<T> getResultType()这样的方法,您就可以编写:

return command.getResultType().newInstance(); // instead of new SpecificResult()

这当然意味着在每个Result实现中都有一个默认构造函数,依此类推…

一种对OO更友好的方法(无反射)是让命令实例化自己的结果(使用工厂方法T instantiateResult()):

return command.instantiateResult();

稍微扩展一下SimonC的答案。

考虑你有类:

Result
LoginResult extends Result
OtherResult extends Result

如果您的类型T是OtherResult,那么尝试返回LoginResult是无稽之谈。编译器只有在编译时能够保证敏感度的情况下才会编译它。就目前情况来看,它不能,因为T可能是与LoginResult不兼容的类型。

返回类型为<T extends Result>并不意味着您必须返回属于Result的内容。这意味着您需要返回T,但T必须是Result的子类

关于您的编辑

Edit:我想使用泛型的原因是这样我就可以做一些事情以下内容:

Command<LoginResult> login = new Command<>();
LoginResult result = execute( login );

我不确定execute到底应该做什么,但我的第一个想法是让execute成为Command的一个实例方法。

然后你会有

public T execute()
{
}

问题是您需要一种实例化LoginResult的方法。这就是我们需要更多关于您具体问题的信息以给出详细答案的地方。

我会在Result中创建一个名为newInstance的静态方法。既然知道T是结果的某个子类,就可以调用T.newInstance()。然后,loginResult的潜在构造函数可以是私有的,您可以通过它的newInstance方法调用它。

这需要将您的命令定义为:

public class Command<T extends Result>

Result必须有一个带有签名的方法:

public static Result newInstance()

可能出现的另一个问题是,您不希望将"命令"约束为"结果"。没关系,你可以创建一个新类:

public class ResultCommand<T extends Result> extends Command

比未检查的强制转换更安全的解决方案:

public <T extends Result> T execute(Command<T> command, Class<T> cls)
{
return cls.cast(new LoginResult());
}
LoginResult result = execute(loginCommand, LoginResult.class);

具有这两个参数的理由是execute的合同隐含为:

  • 任一使用Command方法来获得类型为T的结果以返回该结果
  • 以其他方式创建一个结果,但通过下转换确保其类型为T

使用Java的泛型,只有形式为<T> T foo(Bar<T>, Baz<T>)的泛型方法的参数才能安全地"创建"类型为T的对象。因此,如果在您的情况下,您没有使用command参数创建结果,那么您需要另一个可以负责类型检查的参数。

编译器需要知道返回值将匹配类型T,但它不能。如果您将Command作为参数传递,它将匹配,但如果您传递Command,它可能不匹配,因为T将被定义为其他类型。

您可以在运行时强制转换它,如果您返回的对象与T.不匹配,它将抛出ClassCastException

public <T extends Result> T execute(Command<T> command, Class<T> type)
{
return type.cast( new LoginResult() );
}

你可以这样称呼它:

Command<LoginResult> login = new Command<>();
LoginResult result = execute( login, LoginResult.class );

请注意这里的冗余信息,但这是必需的,因为泛型在编译中会丢失。

最新更新