方法引用表达式的条件为 "exact"

Conditions for Method Reference Expression to be "exact"

考虑 JLS 中的以下文章 (§15.13.1)

A method reference expression ending with Identifier is exact if it satisfies all of the following:

  • If the method reference expression has the form ReferenceType ::[TypeArguments] Identifier, then ReferenceType does not denote a raw type.
  • The type to search has exactly one member method with the name Identifier that is accessible to the class or interface in which the method reference expression appears.
  • This method is not variable arity (§8.4.1).
  • If this method is generic (§8.4.4), then the method reference expression provides TypeArguments.

考虑以下代码片段:

class Scratch {

  public static void main(String[] args) {
    Scratch.funct(new ImplementingClass()::<Functional1>hitIt);
  }

  public static void funct(Functional1 a){}
  public static void funct(Functional2 a){}
}
interface Functional1 {<T> T hitIt();}
interface Functional2 {<T> T hitIt();}

class ImplementingClass{
  public <T> T hitIt(){return null;}
}

很明显 - 这满足了为准确引用方法而提到的所有条件。

不确定为什么在这种特殊情况下方法引用仍然不准确?我是否遗漏了条款中的某些内容?

解决方案:

根据@Sweeper @DidierL 和@Holger 的意见,我总结如下:

  1. 两个功能接口都有 functionType <T> () -> T
  2. 方法参考 …::<Functional1>hitItT 替换为 Functional1,因此生成的函数签名为 () -> Functional1,与 <T> () -> T.[=38 不匹配=]

首先是一个警告:IANAJL(Java 的 IANAL)

据我所知,如果你创建两个接口方法non-generic,这应该可以编译,但它没有。让我们尽可能地简化代码以重现问题:

class Scratch {
  public static void main(String[] args) {
    Scratch.funct(ImplementingClass::<Void>hitIt);
  }

  public static void funct(Functional1 a){}
  public static void funct(Functional2 a){}
}
interface Functional1 {Integer hitIt();}
interface Functional2 {String hitIt();}

class ImplementingClass{
  public static <T> Integer hitIt(){return null;}
}

简化:

  • 这两个接口现在有 non-generic 方法
  • ImplementingClass.hitIt() 现在是静态的并且具有具体的 return 类型 (non-generic)

现在让我们分析调用以检查它是否应该编译。我放了指向 Java 8 规格的链接,但它们在 17.

中非常相似

15.12.2.1. Identify Potentially Applicable Methods

A member method is potentially applicable to a method invocation if and only if all of the following are true:

[…]

  • If the member is a fixed arity method with arity n, the arity of the method invocation is equal to n, and for all i (1 ≤ in), the i'th argument of the method invocation is potentially compatible, as defined below, with the type of the i'th parameter of the method.

[…]

An expression is potentially compatible with a target type according to the following rules:

[…]

  • A method reference expression (§15.13) is potentially compatible with a functional interface type if, where the type's function type arity is n, there exists at least one potentially applicable method for the method reference expression with arity n (§15.13.1), and one of the following is true:
    • The method reference expression has the form ReferenceType :: [TypeArguments] Identifier and at least one potentially applicable method is i) static and supports arity n, or ii) not static and supports arity n-1.
    • The method reference expression has some other form and at least one potentially applicable method is not static.

(最后一个项目符号适用于方法引用使用构造函数调用表达式的问题,即 Primary

此时,我们只检查方法引用的完整性,因此 funct() 两种方法都可能适用。

15.12.2.2. Phase 1: Identify Matching Arity Methods Applicable by Strict Invocation

An argument expression is considered pertinent to applicability for a potentially applicable method m unless it has one of the following forms:

[…]

  • An inexact method reference expression (§15.13.1).

[…]

这是此列表中唯一可能匹配的要点,但是,正如问题中所指出的,我们这里有一个 exact 方法引用表达式。请注意,如果您删除 <Void>,这会使它成为一个不准确的方法引用,并且这两种方法都应该适用于下一节:

Let m be a potentially applicable method (§15.12.2.1) with arity n and formal parameter types F1 ... Fn, and let e1, ..., en be the actual argument expressions of the method invocation. Then:

[…]

  • If m is not a generic method, then m is applicable by strict invocation if, for 1 ≤ in, either ei is compatible in a strict invocation context with Fi or ei is not pertinent to applicability.

然而,只有第一个 funct() 方法声明应该适用于严格调用。定义了严格的调用上下文 here, but basically they check if the type of the expression matches the type of the argument. Here the type of our argument, the method reference, is defined by section 15.13.2. Type of a Method Reference 其相关部分是:

A method reference expression is compatible in an assignment context, invocation context, or casting context with a target type T if T is a functional interface type (§9.8) and the expression is congruent with the function type of […] T.

[…]

A method reference expression is congruent with a function type if both of the following are true:

  • The function type identifies a single compile-time declaration corresponding to the reference.

  • One of the following is true:

    • The result of the function type is void.
    • The result of the function type is R, and the result of applying capture conversion (§5.1.10) to the return type of the invocation type (§15.12.2.6) of the chosen compile-time declaration is R' (where R is the target type that may be used to infer R'), and neither R nor R' is void, and R' is compatible with R in an assignment context.

此处 R 对 Functional1Integer,对 Functional2String,而 R' 在这两种情况下都是 Integer(因为ImplementingClass.hitIt()) 不需要捕获转换,因此很明显,方法引用 Functional2 不一致 并且扩展不兼容。

因此,

funct(Functional2) 不应被严格调用视为适用性,并且由于仅 funct(Functional1) 仍然存在,因此应该 selected.

需要注意的是Javac必须selectPhase 1中的两种方法,因为只有一个phase可以应用,而Phase 2只使用loose context 而不是 strict,它只允许装箱操作,然后阶段 3 包括可变参数,这也不适用。

除非我们认为 Javac 以某种方式认为方法引用与 Functional2 一致,我认为 selecting 这两种方法的唯一原因是它是否考虑了方法引用为与上面指定的适用性不相关,我只能在编译器认为它是不精确的方法引用时解释。

15.12.2.5. Choosing the Most Specific Method

这是编译失败的地方。我们应该注意这里没有任何东西可以使编译器 select 一种方法优于另一种方法。适用的规则是:

  • m2 is not generic, and m1 and m2 are applicable by strict or loose invocation, and where m1 has formal parameter types S1, ..., Sn and m2 has formal parameter types T1, ..., Tn, the type Si is more specific than Ti for argument ei for all i (1 ≤ i ≤ n, n = k).

[…] A type S is more specific than a type T for any expression if S <: T (§4.10).

这似乎可以正常工作:将 Functional2 更改为扩展 Functional1,它将编译。

A functional interface type S is more specific than a functional interface type T for an expression e if T is not a subtype of S and one of the following is true (where U1 ... Uk and R1 are the parameter types and return type of the function type of the capture of S, and V1 ... Vk and R2 are the parameter types and return type of the function type of T):

  • If e is an explicitly typed lambda expression […]
  • If e is an exact method reference expression (§15.13.1), then i) for all i (1 ≤ i ≤ k), Ui is the same as Vi, and ii) one of the following is true:
    • R2 is void.
    • R1 <: R2.
    • […]

这也不允许消除歧义。但是,将 Functional2.hitIt() 更改为 return Number 应该会使 Functional1 更具体,因为 Integer <: Number.

这仍然失败,这似乎证实了编译器并未将其视为确切的方法引用。

请注意,删除 ImplementingClass.hitIt() 中的 <T> 允许它编译,独立于 Functional2.hitIt() 的 return 类型。有趣的事实:您可以在调用站点保留 <Void>,编译器会忽略它。

更奇怪的是:如果你离开 <T> 并在调用站点添加比所需更多的类型参数,编译器仍然会抱怨不明确的调用而不是类型参数的数量(直到你删除歧义)。根据上述定义,并不是说这会使方法引用不准确,但我认为 it should be checked first.

结论

由于 Eclipse 编译器接受它,我倾向于将其视为 Javac 错误,但请注意,Eclipse 编译器有时在规范方面比 Javac 更宽松,并且已报告并关闭了一些类似的错误 (JDK-8057895, JDK-8170842, …)。