内联所选方法引用的调用方法

Call method on chosen method reference inline

我有以下无法编译的程序:

Just block 1 编译正常并按预期工作 - 我可以有条件地 select 一个对象并在其上内联调用一个方法。

Just block 2 也可以正常编译并按预期工作 - 我可以有条件地将方法引用分配给 Supplier<String> 变量并对该变量调用 .get()

但是块 3 编译失败:

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

我认为结合块 1 和块 2 中的想法我将能够执行块 3,因为 ((Supplier<String>) (args.length > 0 ? Lambda::foo : Lambda::bar)) 的类型是 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);
  }

}

需要说明的是:我不是在这里寻找解决方法,这首先不是高质量的代码。我只是好奇为什么最后一个块无法编译。

我认为下面这行代码不起作用的原因仅仅是类型推断问题。

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

因此可以通过如下显式转换来解决:

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

原因是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.

由于casting contexts不在列表中,引用条件表达式在该上下文中是一个独立的表达式,这意味着它的结果类型仅由其参数类型决定。由于方法引用本身没有类型,而是依赖于目标类型,因此不能在此处使用它们(没有其他类型提供构造)。

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

因此,虽然转换上下文通常是方法引用的有效位置,但由于转换上下文中条件的独立表达式性质,转换上下文和条件表达式的组合被证明是无效的。

除非,我们在表达式中提供显式类型,例如 with
args.length > 0 ? (Supplier<String>)Lambda::foo : (Supplier<String>)Lambda::bar,当然。

除了 lambda 表达式或方法引用之外,当它们可以是多边形表达式时,也可以用其他示例来证明此规则的结果:

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

我不知道为什么转换上下文不符合将引用条件表达式作为多边形表达式的条件,但这就是 Java 8 到 Java 11 的指定方式……