考慮以下來自 JLS 的文章(§15.13.1)
如果滿足以下所有條件,則以 Identifier 結尾的方法參考運算式是精確的:
- 如果方法參考運算式的格式為 ReferenceType ::[TypeArguments] Identifier,則 ReferenceType 不表示原始型別。
- 要搜索的型別只有一個名為 Identifier 的成員方法,該方法參考運算式所在的類或介面可以訪問該方法。
- 此方法不是可變引數(第 8.4.1 節)。
- 如果此方法是通用的(第 8.4.4 節),則方法參考運算式提供 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;}
}
顯然 - 這滿足了為準確地參考方法而提到的所有條件。
不確定為什么在這種特殊情況下方法參考仍然不準確?我在條款中遺漏了什么嗎?
解決方案 :
Based on inputs from @Sweeper @DidierL and @Holger here what I summarized:
- Both the functional interfaces have the functionType
<T> () -> T - the method reference
…::<Functional1>hitItsubstitutesTwithFunctional1, so the resulting functional signature is() -> Functional1which does not match<T> () -> T.
uj5u.com熱心網友回復:
首先是一個警告:IANAJL (IANAL for Java ??)
據我所知,如果您將兩個介面方法設為非泛型,這應該可以編譯,但事實并非如此。讓我們盡可能簡化代碼以重現問題:
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;}
}
簡化:
- 這兩個介面現在有非泛型方法
ImplementingClass.hitIt()現在是靜態的并且有一個具體的回傳型別(非泛型)
現在讓我們分析呼叫以檢查它是否應該編譯。我放了 Java 8 規范的鏈接,但它們在 17 中非常相似。
15.12.2.1。確定可能適用的方法
當且僅當滿足以下所有條件時,成員方法才可能適用于方法呼叫:
[…]
- 如果成員是具有 arity n的固定 arity 方法,則方法呼叫的 arity 等于n,并且對于所有i (1 ≤ i ≤ n ),方法呼叫的第i個引數是潛在兼容的,如定義下面,使用方法的第i個引數的型別。
[…]
根據以下規則,運算式可能與目標型別兼容:
[…]
- 方法參考運算式(§15.13)可能與功能介面型別兼容,如果型別的函式型別 arity 為n,則至少存在一種可能適用于方法參考運算式的方法,該方法參考運算式具有 arity n(§15.13.1),以下情況之一為真:
- 方法參考運算式的格式為ReferenceType :: [TypeArguments] Identifier并且至少一個可能適用的方法是 i)
static并支持 arity n,或 ii) 不static支持 arity n -1。- 方法參考運算式具有某種其他形式,并且至少一種可能適用的方法不是
static。
(this last bullet applies for the case of the question where the method reference uses a constructor invocation expression, i.e. a Primary)
At this point, we only check for the arity of the method reference, so both funct() methods are potentially applicable.
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).
[…]
This is the only bullet point in this list that could potentially match, however, as pointed in the question we have an exact method reference expression here. Note that if you remove the <Void>, this makes it an inexact method reference, and both methods should be applicable as per the next section:
Let
mbe a potentially applicable method (§15.12.2.1) with arity n and formal parameter typesF1...Fn, and lete1, ...,enbe the actual argument expressions of the method invocation. Then:[…]
- If
mis not a generic method, thenmis applicable by strict invocation if, for 1 ≤ i ≤ n, eithereiis compatible in a strict invocation context withFioreiis not pertinent to applicability.
However only the first funct() method declaration should be applicable by strict invocation. Strict invocation contexts are defined 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 whose relevant part is:
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.
Here R would be Integer for Functional1 and String for Functional2, while R' is Integer in both cases (since there is no capture conversion needed for ImplementingClass.hitIt()), so clearly the method reference is not congruent with Functional2 and by extension not compatible.
funct(Functional2) should thus not be considered for applicability by strict invocation, and since only funct(Functional1) remains it should be selected.
It should be noted that Javac must select both methods in Phase 1, because only one phase can apply, and Phase 2 only uses loose context instead of strict, which just allows boxing operations, and Phase 3 then includes varargs, which is not applicable either.
Except if we consider that Javac somehow considers the method reference as congruent with Functional2, the only reason I see for selecting both methods is if it considered the method reference as not pertinent for applicability as specified above, which I can only explain if the compiler considers it as an inexact method reference.
15.12.2.5. Choosing the Most Specific Method
This is where the compilation fails. We should note that there is nothing here that would make the compiler select one method over the other. The applicable rule is:
- 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).
This appears to work properly: change Functional2 to extend Functional1 and it will compile.
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.
- […]
This does not allow to disambiguate it either. However, changing Functional2.hitIt() to return Number should make Functional1 more specific since Integer <: Number.
This still fails, which seems to confirm that the compiler does not consider it as an exact method reference.
Note that removing the <T> in ImplementingClass.hitIt() allows it to compile, independently of the return type of Functional2.hitIt(). Fun fact: you can leave the <Void> at the call site, the compiler ignores it.
Even stranger: if you leave the <T> and add more type arguments than required at the call site, the compiler still complains about the ambiguous call and not about the number of type arguments (until you remove the ambiguity). Not that this should make the method reference inexact, based on the above definition, but I would think it should be checked first.
Conclusion
Since the Eclipse compiler accepts it, I would tend to consider this as a Javac bug, but note that the Eclipse compiler is sometimes more lenient than Javac with respect to the specs, and some similar bugs have been reported and closed (JDK-8057895, JDK-8170842, …).
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/437854.html
標籤:java generics type-inference method-reference jls
上一篇:如何將自定義ObjectMapper序列化程式限制為特定的泛型型別
下一篇:如何將型別限制為具有索引的型別?
