Tomas
Tomas

Reputation: 3436

How Java 8 is able to infer lambdas argument type

I'm currently playing with Vert.x in Java and noticed that the examples in the documentation use lambdas extensively as callback parameters. For example:

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!");
  }
});

Looking into the documentation of listen functions shows the following:

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

My question is how does the JVM have a chance to deduce generic data types such as Handler<AsyncResult<NetServer>> out of such non-informative objects such as res? This seems fine for languages like JavaScript that do duck-typing, but for languages like Java that do strong typing, it's not as obvious for me. If we use an anonymous class instead of a lambda, all data types would be on the plate.

--EDIT-- As already explained by @Zircon, probably better example from Vert.x documentation would be following declaration:

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

with example of usage from 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());
});

Where type of is not available, thus only methods available on Future and AsyncResults can be used.

Upvotes: 3

Views: 1752

Answers (1)

Zircon
Zircon

Reputation: 4707

The compiler infers the type in the same exact way you do.

Netserver.listen takes a Handler<AsyncResult<NetServer>> as its third parameter.

Handler is a vertx FunctionalInterface with one method handle(E event). In this case, E is AsyncResult<NetServer>.

Inserting a lambda here makes it take the place of Handler.handle. Therefore, the single argument res must be of type AsyncResult<NetServer>. This is why it can then call AsyncResult.succeeded without issue.

Simply:

It is impossible for the third argument of listen to be anything but Handler<AsyncResult<NetServer>>, so the lambda must be offering an argument of type <AsyncResult<NetServer>>.

Edit:

About using nested generics in a lambda, consider this class:

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

(In this case, we don't care about the stuff happening.)

However, consider how we need to call this method. We need to have an instance of MyClass, which also means we need to declare the generic type before we call 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!");
  }
});

In this case, the compiler can still infer res as an AsyncResult<String>, because T is String in this case. If I unwrapped the AsyncResult, I could then call String methods like toUpperCase and whatnot.

If you end up referencing a MyClass<?> and attempt to similarly make use of the lambda, res will be inferred as an AsyncResult<?>. (You can unwrap the ? type, but because it's type can't be known at compile-time you are forced to treat it is as an Object.)

If we don't declare a generic type during declaration, we will get a warning about it and as a consequence of raw typing this code won't work (Thanks 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!");
  }
});

Because we've declared myObj as a raw type of MyClass, res becomes of type Object (Not AsyncResult<Object>), so we can't call succeeded on it.

In this way, it is impossible for you to use a lambda without knowing exactly what type you're inferring with its arguments.

There might be some advanced ways of using a lambda in place of a method whose generic type is declared in its own signature, but I'd need to do some research to illustrate these points. Essentially, even if this could occur, you would need to call MyClass.<MyType>doSomethingStatic in order to declare the type before declaring the lambda so that the type can be inferred.

Simply:

You cannot use a lambda where the type cannot be inferred. Generics do not change this.

Upvotes: 6

Related Questions