Java 8如何能够推断出lambdas参数类型

时间:2017-08-03 12:40:38

标签: java lambda java-8 vert.x

我目前正在使用Java中的Vert.x,并注意到文档中的示例广泛使用lambdas作为回调参数。例如:

NetServer server = vertx.createNetServer();
server.listen(1234, "localhost", res -> {
  if (res.succeeded()) {
    System.out.println("Server is now listening!");
  } else {
    System.out.println("Failed to bind!");
  }
});

查看listen函数的文档显示以下内容:

NetServer listen(int port,
                 String host,
                 Handler<AsyncResult<NetServer>> listenHandler)

我的问题是JVM如何有机会从诸如Handler<AsyncResult<NetServer>>之类的非信息性对象中推导出res等通用数据类型?对于像鸭子这样打字的JavaScript这样的语言来说这似乎很好,但是对于像Java那样做强打字的语言,它对我来说并不那么明显。如果我们使用匿名类而不是lambda,那么所有数据类型都将在板上。

- EDIT-- 正如@Zircon已经解释的那样,Vert.x文档中可能更好的例子是声明:

<T> void executeBlocking(Handler<Future<T>> blockingCodeHandler,
                         Handler<AsyncResult<T>> resultHandler)

以及来自docs的用法示例:

vertx.executeBlocking(future -> {
  // Call some blocking API that takes a significant amount of time to return
  String result = someAPI.blockingMethod("hello");
  future.complete(result);
}, res -> {
  System.out.println("The result is: " + res.result());
});

如果类型不可用,则只能使用FutureAsyncResults上可用的方法。

1 个答案:

答案 0 :(得分:5)

编译器以与您完全相同的方式推断出类型。

Netserver.listenHandler<AsyncResult<NetServer>>作为其第三个参数。

Handler是一个vertx FunctionalInterface,有一个方法handle(E event)。在这种情况下,EAsyncResult<NetServer>

在此处插入lambda使其取代Handler.handle。因此,单个参数res必须是AsyncResult<NetServer>类型。这就是为什么它可以毫无问题地调用AsyncResult.succeeded

简单地:

listen的第三个参数不可能只是Handler<AsyncResult<NetServer>>,所以lambda必须提供<AsyncResult<NetServer>>类型的参数。

修改

关于在lambda中使用嵌套泛型,请考虑以下类:

public class myClass<T> {
    public void doSomething(int port, String host, Handler<AsyncResult<T>> handler) {
        //Stuff happens
    }
}

(在这种情况下,我们不关心发生的事情。)

但是,请考虑我们如何调用此方法。我们需要有一个MyClass实例,这也意味着我们需要在调用doSomething之前声明泛型类型:

MyClass<String> myObj = new MyClass<String>();
result = myObj.doSomething(port, host, res -> {
  if (res.succeeded()) {
    System.out.println("I did a thing!");
  } else {
    System.out.println("I did not do a thing!");
  }
});

在这种情况下,编译器可以推断resAsyncResult<String>,因为在这种情况下TString。如果我打开AsyncResult,我可以调用StringtoUpperCase等方法。

如果您最终引用MyClass<?>并尝试同样使用lambda,则res将被推断为AsyncResult<?>。 (您可以打开?类型,但因为在编译时无法识别它的类型,所以您被迫将其视为Object。)

如果我们在声明期间没有声明泛型类型,我们会收到有关它的警告,并且由于原始输入,此代码将无效(感谢Holger):

MyClass myObj = new MyClass(); //Generic type warning
result = myObj.doSomething(port, host, res -> {
  if (res.succeeded()) { //Error
    System.out.println("I did a thing!");
  } else {
    System.out.println("I did not do a thing!");
  }
});

因为我们已将myObj声明为MyClass的原始类型,res变为Object类型(非AsyncResult<Object>),所以我们可以'打电话给succeeded

通过这种方式,你不可能在不知道你用其参数推断出什么类型的情况下使用lambda。

可能有一些使用lambda代替泛型类型在其自己的签名中声明的方法的高级方法,但我需要做一些研究来说明这些要点。基本上,即使可能发生这种情况,您也需要调用MyClass.<MyType>doSomethingStatic以声明类型,然后声明lambda,以便可以推断出类型。

简单地:

您无法使用无法推断出类型的lambda。泛型不会改变这一点。