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

Tom*_*mas 2 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!");
  }
});
Run Code Online (Sandbox Code Playgroud)

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

NetServer listen(int port,
                 String host,
                 Handler<AsyncResult<NetServer>> listenHandler)
Run Code Online (Sandbox Code Playgroud)

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

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

<T> void executeBlocking(Handler<Future<T>> blockingCodeHandler,
                         Handler<AsyncResult<T>> resultHandler)
Run Code Online (Sandbox Code Playgroud)

以及来自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());
});
Run Code Online (Sandbox Code Playgroud)

如果没有类型,则只能使用FutureAsyncResults可以使用的方法.

Zir*_*con 5

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

Netserver.listen将a Handler<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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

但是,请考虑我们如何调用此方法.我们需要有一个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!");
  }
});
Run Code Online (Sandbox Code Playgroud)

在这种情况下,编译器可以仍然推断resAsyncResult<String>,因为TString在这种情况下.如果我解开了AsyncResult,我可以调用String类似于toUpperCase诸如此类的方法.

如果您最终引用a 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!");
  }
});
Run Code Online (Sandbox Code Playgroud)

因为我们已经声明myObj为原始类型MyClass,res变为类型Object(Not AsyncResult<Object>),所以我们不能调用succeeded它.

这样,你就不可能在不知道你用它的参数推断出什么类型的情况下使用lambda.

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

只是:

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

  • 更正:*原始类型*`MyClass`被**处理为`MyClass <Object>`.原始类型根本不使用泛型.相反,接口的推断类型将是*raw类型*`Handler`,而`res`将不是`AsyncResult <Object>`,而只是`Object`.这就是为什么你永远不应该使用*原始类型*.如果你使用`MyClass <?>`,它表示一个未知类型的参数,Generics将工作,`res`将是`AsyncResult <?>`. (4认同)
  • @Zircon它不是推断类型的JVM,而是编译器 (3认同)
  • 正确的,OP的误解是`Handler <AsyncResult <NetServer >>`是从`res`推导出来的,但实际上`res`是通过使用`listen`方法的声明推断为`Handler <AsyncResult <NetServer >>`. (2认同)