burak emre
burak emre

Reputation: 1591

Why Java chooses Object parameterized type when using generics with lambda?

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.

Upvotes: 3

Views: 2580

Answers (2)

newacct
newacct

Reputation: 122439

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>.

Upvotes: 3

Jesper
Jesper

Reputation: 206796

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();

Upvotes: 5

Related Questions