3

Let's say I have method which takes a java.util.function.Predicate and return CompletableFuture:

public <R> CompletableFuture<R> call(Predicate<R> request) {
    return new CompletableFuture<>();
}

If I call this method using an anonymous class like this:

Integer a = call(new Predicate<Integer>() {
    @Override
    public boolean test(Integer o) {
        return false;
    }
}).join();

It works because I have to explicitly declare the type of the Predicate. However, if I use lambda expression instead of anonymous class like this:

Integer a = call(o -> false).join();

It doesn't compile because Java thinks that it's a Predicate<Object> and returns a message like this:

Error:(126, 42) java: incompatible types: java.lang.Object cannot be converted to java.lang.Integer

There are a few workarounds that I found. I may create the CompletableFuture variable explicitly instead of chaining, or add an unnecessary extra argument Class<R> that tells Java which type we want to get or force the type in lambda expression.

However I wonder why Java chooses Object instead of Integer in the first lambda example, it already knows which type I want to get so the compiler may use the most specific type instead of Object because all of the three workarounds seems ugly to me.

burak emre
  • 1,501
  • 4
  • 22
  • 46

2 Answers2

5

There are limits to Java 8's type inference, and it does not look at the type of the variable that you assign the result to. Instead, it infers the type Object for the type parameter.

You can fix it by explicitly specifying the type of the argument o in the lambda:

Integer a = call((Integer o) -> false).join();
Jesper
  • 202,709
  • 46
  • 318
  • 350
  • Yes, this is one the workarounds that I suggested but I think it's also ugly because if I have multiple arguments in lambda expression, I have to declare the types for all arguments. Let's consider this example: `ConsistentHashRing remoteRing = ctx.ask(oneMemberOfCluster, (RingMap service, OperationContext ctx1) -> ctx1.reply(service.getRing())).join();` – burak emre Dec 25 '14 at 14:23
  • 4
    @burakemre There's no free lunch. Type inference needs some type information to work with. It is pretty good at finding some from most programs, but if you don't provide it anything to work with, it can't do its magic. Adding additional type information isn't a "workaround"; it's providing type inference with enough information to solve the problem you've asked it to. Adding manifest types on a lambda parameter once in a while is really not that intrusive. You can also add explicit type witnesses on the call method: `receiver.call(...)` in cases where that would be less intrusive. – Brian Goetz Dec 25 '14 at 16:07
  • @BrianGoetz I don't really understand why it's hard to find out the type inference for the given scenario. If CompletableFuture.join() returns the parameterized type and if the type is inferred from somewhere else, I assumed Java can figure out by looking at how I used the variable returned by call(Predicate). In another question, (http://stackoverflow.com/a/25173599/689144) it's referred as a weakness and AFAIK Scala doesn't suffer from this problem. However I'm just a beginner in the area of language semantics so if you know any article about it, it would be great if you could share it. – burak emre Dec 25 '14 at 16:20
  • 2
    @Burak emre see https://jcp.org/aboutJava/communityprocess/final/jsr335/index.html section D. Java 8 significantly enhanced Java 8 type inference, but not enough for chained expression inference like this. – Louis Wasserman Dec 25 '14 at 18:48
  • 3
    "There has been some interest in allowing inference to "chain": in a().b(), passing type information from the invocation of b to the invocation of a. This adds another dimension to the complexity of the inference algorithm, as partial information has to pass in both directions; it only works when the erasure of the return type of a() is fixed for all instantiations (e.g. List). This feature would not fit very well into the poly expression model, since the target type cannot be easily derived; but perhaps with additional enhancements it could be added in the future." – Louis Wasserman Dec 25 '14 at 18:50
3

Other people have answered how to force the lambda to the right type. However, I would argue that Predicate<Object> is not "wrong" for that predicate and you should be allowed to use it -- you have a predicate on all objects -- it should work on all objects including Integers; why do you need to make a more restricted predicate on Integers? You shouldn't.

I would argue that the real problem is the signature of your call method. It's too strict. Predicate is a consumer of its type parameter (it only has a method that takes its type parameter type and returns boolean); therefore, according to the PECS rule (Producer extends, consumer super), you should always use it with a super bounded wildcard.

So you should declare your method like this:

public <R> CompletableFuture<R> call(Predicate<? super R> request)

No matter what code you had in this method, it will still compile after changing the bound to super, because Predicate is a consumer. And now, you don't need to force it to be Predicate<Integer> -- you can use a Predicate<Object> and still have it return a CompletableFuture<Integer>.

newacct
  • 119,665
  • 29
  • 163
  • 224