Reputation: 3436
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
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