6
votes

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.

2
Which version of javac are you using? - Mark Rotteveel
Version please, it fails to compile for me in both ways. Java-8 - Naman
javac 1.8.0_172 and javac 10.0.2. I know Java 10 is not recent, but I have it installed, so.. - Johannes Kuhn
Which Java implementation do you use? Where did you get it from, Oracle? - Zabuzard
java version "1.8.0_172" Java(TM) SE Runtime Environment (build 1.8.0_172-b11) Java HotSpot(TM) 64-Bit Server VM (build 25.172-b11, mixed mode) - Johannes Kuhn

2 Answers

4
votes

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);
4
votes

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.