如何在Scala中重写Elasticsearch 2.4.x的FilterClient#doExecute()
Hey,我碰到过类似Scala与Java泛型交互的坑,尤其是在Elasticsearch 2.x这种老版本里!让我帮你理清楚问题出在哪,以及怎么解决。
问题回顾
你现在卡在Elasticsearch 2.4.x版本(依赖org.elasticsearch % "elasticsearch" % "2.4.x"),没法升级——毕竟后续版本里ActionRequest的泛型参数被移除了,虽然新版本的写法能正常运行,但暂时没法动版本对吧?
你要重写的Java父类方法是这样的:
protected <Request extends ActionRequest, Response extends ActionResponse, RequestBuilder extends ActionRequestBuilder<Request, Response, RequestBuilder>> void doExecute(Action<Request, Response, RequestBuilder> action, Request request, ActionListener<Response> listener) { in().execute(action, request, listener); }
然后你写了Scala版本的重写代码,结果在Scala 2.11.11、2.11.12、2.12.4里都编译失败:
import org.elasticsearch.action._ import org.elasticsearch.client.{Client, FilterClient} class DemoFilterClient(underlyingClient: Client) extends FilterClient(underlyingClient) { override def doExecute[ Request <: ActionRequest[_], Response <: ActionResponse, RequestBuilder <: ActionRequestBuilder[Request, Response, RequestBuilder] ]( action: Action[Request, Response, RequestBuilder], request: Request, listener: ActionListener[Response] ) = super.doExecute(action, request, listener) }
编译错误信息看起来挺吓人的:
[info] Compiling 1 Scala source to /home/roberto/development/elasticsearch-scala-client-test/target/scala-2.11/classes ...
[error] /home/roberto/development/elasticsearch-scala-client-test/src/main/scala/com/gu/DemoFilterClient.scala:7:101: type arguments [Request,Response,RequestBuilder] do not conform to class ActionRequestBuilder's type parameter bounds [Request <: org.elasticsearch.action.ActionRequest[_ <: org.elasticsearch.action.ActionRequest[_ <: org.elasticsearch.action.ActionRequest[_ <: AnyRef]]],Response <: org.elasticsearch.action.ActionResponse,RequestBuilder <: org.elasticsearch.action.ActionRequestBuilder[Request,Response,RequestBuilder]]
[error] override def doExecute[Request <: ActionRequest[_], Response <: ActionResponse, RequestBuilder <: ActionRequestBuilder[Request, Response, RequestBuilder]](action: Action[Request, Response, RequestBuilder], request: Request, listener: ActionListener[Response]) = super.doExecute(action, request, listener)
[error] ^
[error] one error found
说白了就是:Scala把Java的泛型边界给过度解析了,尤其是Request的类型被展开成了多层递归的通配符,导致你的泛型约束匹配不上。
解决方法
问题的核心是Scala与Java泛型对递归边界的处理方式不同,我们需要调整Scala代码里的泛型约束,让类型检查器能正确匹配Java的定义。
方案一:用通配符替代ActionRequestBuilder的第一个参数
修改后的代码如下:
import org.elasticsearch.action._ import org.elasticsearch.client.{Client, FilterClient} class DemoFilterClient(underlyingClient: Client) extends FilterClient(underlyingClient) { override def doExecute[ Request <: ActionRequest[_], Response <: ActionResponse, RequestBuilder <: ActionRequestBuilder[_, Response, RequestBuilder] ]( action: Action[Request, Response, RequestBuilder], request: Request, listener: ActionListener[Response] ): Unit = super.doExecute(action, request, listener) }
方案二:明确Request的边界为ActionRequest[_ <: AnyRef]
如果你想要更贴近Java的泛型声明,也可以这样写:
import org.elasticsearch.action._ import org.elasticsearch.client.{Client, FilterClient} class DemoFilterClient(underlyingClient: Client) extends FilterClient(underlyingClient) { override def doExecute[ Request <: ActionRequest[_ <: AnyRef], Response <: ActionResponse, RequestBuilder <: ActionRequestBuilder[Request, Response, RequestBuilder] ]( action: Action[Request, Response, RequestBuilder], request: Request, listener: ActionListener[Response] ): Unit = super.doExecute(action, request, listener) }
为什么这两种方法能行?
- 方案一里,我们把
ActionRequestBuilder[Request, Response, RequestBuilder]改成了ActionRequestBuilder[_, Response, RequestBuilder],用通配符_来兼容Scala过度解析的递归边界,让类型检查器不再纠结Request的多层嵌套约束。 - 方案二则是明确了Request的边界是
ActionRequest[_ <: AnyRef],直接匹配错误信息里最内层的_ <: AnyRef约束,避免Scala展开成多层递归。 - 另外,显式指定返回类型
: Unit也是个好习惯,虽然Scala能自动推断,但在重写Java方法时,显式声明能避免一些隐式类型转换的问题。
内容的提问来源于stack exchange,提问作者Roberto Tyley

