'Why does it matter if I use a method reference or a lambda here?

When I try to compile this code

import java.util.Optional;

public class GenericTest {

    public static void main(String[] args) {
        Optional.empty().map(o -> getStringClass(o)).orElse(String.class);
    }

    static Class<?> getStringClass(Object arg) {
        return String.class;
    }

}

javac will fail with the following error:

GenericTest.java:6: error: method orElse in class Optional cannot be applied to given types;
                Optional.empty().map(o -> getStringClass(o)).orElse(String.class);
                                                            ^
  required: Class<CAP#1>
  found: Class<String>
  reason: argument mismatch; Class<String> cannot be converted to Class<CAP#1>
  where T is a type-variable:
    T extends Object declared in class Optional
  where CAP#1 is a fresh type-variable:
    CAP#1 extends Object from capture of ?
1 error

But if I use a method reference instead, javac will compile the code just fine:

import java.util.Optional;

public class GenericTest {

    public static void main(String[] args) {
        Optional.empty().map(GenericTest::getStringClass).orElse(String.class);
    }

    static Class<?> getStringClass(Object arg) {
        return String.class;
    }

}

Why does it make a difference if I use a method reference or a lambda expression?

According to my understanding, both the method reference and the lambda have the type Function<Object,Class<?>>, so I don't see the difference here.
The eclipse java compiler (ecj) won't compile both versions by the way.



Solution 1:[1]

Method chaining strikes again. You can read here of why the designers of this feature found it complicated to implement (it has to do with the fact that lambda's/method references are poly expressions - their types are context dependent). Such a feature would require some extra burden on the compiler - and while your example would be a fairly trivial example to solve, javac has to care about a lot more than triviality; thus this is not yet implemented, even in java-12.

The easiest solution IMO, since this has to do with method chaining (and in your case this is possible), is not to chain:

Optional<Class<?>> first = Optional.empty().map(o -> getStringClass(o));
Class<?> second = first.orElse(String.class);

Solution 2:[2]

This is a known limitation of the compiler's type inference system, it doesn't work with chained method invocations like in your first code snippet.

Possible workarounds? Use an explicitly typed lambda expression:

Optional.empty()
        .map((Function<Object, Class<?>>) o -> getStringClass(o))
        .orElse(String.class);

or an exact method reference (as you've already tried):

Optional.empty().map(GenericTest::getStringClass).orElse(String.class);

or add a type witness:

Optional.empty().<Class<?>>map(o -> getStringClass(o)).orElse(String.class);

Related post with a similar issue.

Solution 3:[3]

As others have said it's a limitation with how type infereance works. There is some detail as to why here: https://docs.oracle.com/javase/specs/jls/se8/html/jls-15.html#jls-15.13.2

Basically, when you use the lambda there is no type on the argument you're passing here:

Optional.empty().map(o -> getStringClass(o)).orElse(String.class);

Since there is no type argument (and no ability to provide one) this will be an error. However with method refrences it's able to infer the type from the method parameters so it will work just fine.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Eugene
Solution 2
Solution 3 Equinox