kotlin的Overload resolution ambiguity问题

2,466 阅读3分钟

在用easyrules构建一个日志分析工具时,遇到这么一个问题:

class Rules{
    public Rules(Rule... rules ) {}
    public Rules(Object... rules ) {}
}

在kotlin中直接构建对象时则会直接编译出错:

    val rules = Rules();

Overload resolution ambiguity: public constructor Rules(vararg p0: Any!) defined in org.jeasy.rules.api.Rules public constructor Rules(vararg p0: Rule!) defined in org.jeasy.rules.api.Rules

看起来是编译器无法识别具体应该调用哪个方法。但是在java中构建对像时一切正常:

    Rules rules = new Rules();

反编译可以看到,java字节码中明确生成了Rule型数组。只好翻开JLS和KLS查找,首先是对调用方法的匹配逻辑。

在 JLS的“15.12.2.5 Choosing the Most Specific Method:" 一章找到了如下描述:

If more than one member method is both accessible and applicable to a method invocation, it is necessary to choose one to provide the descriptor for the run- time method dispatch. The Java programming language uses the rule that the most specific method is chosen.
The informal intuition is that one method is more specific than another if any invocation handled by the first method could be passed on to the other one without a compile-time error. In cases such as an explicitly typed lambda expression argument (§15.27.1) or a variable arity invocation (§15.12.2.4), some flexibility is allowed to adapt one signature to the other.

简而言之,java在编译阶段会优先使用函数签名中,类型更明确(more specific)的那个方法。

而KJS中也有类似的描述:

The main rationale in choosing the most specific function from the overload candidate set is the following:
The most specific function can forward itself to any other function from the overload candidate set, while the opposite is not true.
If there are several functions with this property, none of them are the most specific and an ambiguity error should be reported by the compiler.

假如调用为:

    val rules = Rules(FakeRule());
    //or
    Rules rules = new Rules(new FakeRule());

那么两者invoke的逻辑是一致的,实验结果也是如此。

于是问题应该出现在0实参的情况下, JLS中可变参数的匹配的描述:

If m is being invoked with k ≠ n actual argument expressions, or, if m is being invoked with k = n actual argument expressions and the type of the k'th argument expression is not assignment compatible with T[], then the argument list (e1, ..., en-1, en, ..., ek) is evaluated as if it were written as (e1, ..., en-1, new |T[]| { en, ..., ek }), where |T[]| denotes the erasure (§4.6) of T[].

再看KLS:

One of the parameters may be designated as being variable length (aka vararg). A parameter list ( p 1 , … , vararg p i : P i = v i , … , p n) means a function may be called with any number of arguments in the i-th position. These arguments are represented inside function body b as a value p i of type, which is the result of array type specialization of type Array<out Pi >.

kotlin的语言描述并不如java那么详尽。不过看起来这点也是一致的。可变参数会被转化为一个数组传递给被调用的方法,然后自左至右开始评估参数类型是否匹配。

对于如何确定转化的数组类型,结合上下文来看,java默认会选择more special的那一个类型。而kotlin只能明确指定。

那么解决方法就变成为kotlin调用明确指定一个类型的参数:

   val rules = Rules(*arryOf());

对于一个kotlin新手来说,目前的语法水平也就能先写成这样子了😂