10

I have the following program that fails to compile:

Just block 1 compiles fine and works as expected - I can conditionally select an object and call a method on it inline.

Just block 2 also compiles fine and works as expected - I can conditionally assign a method reference to a Supplier<String> variable and call .get() on that variable.

However block 3 fails to compile:

Lambda.java:31: error: method reference not expected here
    String res = ((Supplier<String>) (args.length > 0 ? Lambda::foo : Lambda::bar)).get();
                                                        ^
Lambda.java:31: error: method reference not expected here
    String res = ((Supplier<String>) (args.length > 0 ? Lambda::foo : Lambda::bar)).get();

I would think combining the ideas in block 1 and 2 I would be able to perform block 3 as the type of ((Supplier<String>) (args.length > 0 ? Lambda::foo : Lambda::bar)) is Supplier<String>.

import java.util.function.Supplier;

class Lambda {

  private final String s;

  private Lambda(String s) {
    this.s = s;
  }

  private static String foo() {
    return "foo";
  }

  private static String bar() {
    return "bar";
  }

  private String str() {
    return s;
  }

  public static void main(String... args) {
    // Block 1
    Lambda l1 = new Lambda("x");
    Lambda l2 = new Lambda("y");
    System.out.println((args.length > 0 ? l1 : l2).str());

    // Block 2
    Supplier<String> s = (args.length > 0 ? Lambda::foo : Lambda::bar);
    System.out.println(s.get());

    // Block 3
    String res = ((Supplier<String>) (args.length > 0 ? Lambda::foo : Lambda::bar)).get();
    System.out.println(res);
  }

}

To be clear: I'm not looking for a workaround here, this wouldn't be good quality code in the first place. I'm just curious why the last block fails to compile.

Holger
  • 285,553
  • 42
  • 434
  • 765
iobender
  • 103
  • 5
  • lambda expression like this `Supplier s = (args.length > 0 ? Lambda::foo : Lambda::bar);` is really surprising me, can you point me to any documentation page of writing lambda expression in different ways – Ryuzaki L Dec 05 '18 at 01:13
  • 1
    @Deadpool there is no lambda expression in this example. `Lambda::foo` and `Lambda::bar` are method references and there’s nothing special about them. The expression of the form `args.length > 0? expression1: expression2` is just the ternary operator. – Holger Dec 05 '18 at 11:48

2 Answers2

12

The reason is the following definition in The Java® Language Specification, §15.25.3

15.25.3. Reference Conditional Expressions

A reference conditional expression is a poly expression if it appears in an assignment context or an invocation context (§5.2. §5.3). Otherwise, it is a standalone expression.

Since casting contexts are not among the list, the reference conditional expression is a stand-alone expression in that context, which means, that its result type is only determined by its argument types. As method references don’t have a type on their own, but rely on a target type, they can’t be used here (without another type providing construct).

Compare with §15.13:

Method reference expressions are always poly expressions (§15.2).

It is a compile-time error if a method reference expression occurs in a program in someplace other than an assignment context (§5.2), an invocation context (§5.3), or a casting context (§5.5).

So while a casting context is a valid location for a method reference in general, the combination of casting context and conditional expression turns out to be invalid due to the stand-alone expression nature of the conditional within the casting context.

Unless, we provide explicit types within the expression, like with
args.length > 0 ? (Supplier<String>)Lambda::foo : (Supplier<String>)Lambda::bar, of course.

The consequences of this rule can be demonstrated with other examples than lambda expressions or method references too, when they can be poly expressions:

// poly expression, infers List<Number> for Arrays.asList(0) and 0 is assignable to Number
List<Number> list = args.length>0? Arrays.asList(0): null;

// stand-alone expression, fails with "List<Integer> cannot be converted to List<Number>"
List<Number> list = (List<Number>)(args.length>0? Arrays.asList(0): null);

I don’t know why the casting context does not qualify for a reference conditional expression to be a poly expression, but that’s how it has been specified for Java 8 to Java 11…

Community
  • 1
  • 1
Holger
  • 285,553
  • 42
  • 434
  • 765
0

I believe the reason why the line of code below is not working is simply due to type inference issues.

String res = ((Supplier<String>) (args.length > 0 ? Lambda::foo : Lambda::bar)).get();

and can therefore be solved by explicitly casting as follows:

String res = (args.length > 0 ? (Supplier<String>)Lambda::foo : (Supplier<String>)Lambda::bar).get();
Ousmane D.
  • 54,915
  • 8
  • 91
  • 126
  • 3
    But shouldn't the type of `((Supplier) (args.length > 0 ? Lambda::foo : Lambda::bar)).get()` be `Supplier`? I fail to see why I can't call `.get()` on that (and why the compiler error message points to that specific spot) – iobender Dec 05 '18 at 01:38