Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Call method on chosen method reference inline

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.

like image 931
iobender Avatar asked Dec 04 '18 23:12

iobender


1 Answers

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…

like image 105
Holger Avatar answered Oct 28 '22 17:10

Holger