На самом деле у вас есть два вида необычной перегрузки операторов. Проблема в том, что компилятор не может знать, какой из них следует использовать. Что если вы хотите вызвать второго оператора с такими же типами? Тип не может быть вычтен.
Что вы можете сделать? Все инфиксные функции могут быть вызваны «обычным» способом. И затем вы можете указать, какой именно метод вы хотите использовать, поскольку компилятор не может быть уверен в этом вызове:
fun test(a: (A) -> Double, b: (B) -> Double): (A, B) -> Double = a.plus<A, B>(b)
fun test(a: (A) -> Double, b: (A) -> Double): (A) -> Double = a.plus<A>(b)
глубокое погружение:
Все кажетсячтобы быть в порядке, потому что компилятор должен быть достаточно умен, чтобы определить тип. Но ...
Лямбды представлены в JVM как kotlin.jvm.functions.Function*
(где * - количество аргументов). И универсальный тип стирается. Если так, давайте декомпилируем сгенерированный код:
public final class OverloadKt {
@NotNull
public static final Function1 plus(@NotNull Function1 $this$plus, @NotNull Function1 f1) {
Intrinsics.checkParameterIsNotNull($this$plus, "$this$plus");
Intrinsics.checkParameterIsNotNull(f1, "f1");
return (Function1)null.INSTANCE;
}
@NotNull
public static final Function2 plus(@NotNull Function1 $this$plus, @NotNull Function1 f2) {
Intrinsics.checkParameterIsNotNull($this$plus, "$this$plus");
Intrinsics.checkParameterIsNotNull(f2, "f2");
return (Function2)null.INSTANCE;
}
}
Нет никакой разницы между их определениями! Что интересно, давайте добавим метод main, который будет выполнять эти методы:
fun main() {
val aLambda = { a: A -> 10.0 }
val bLambda = { b: B -> 20.0 }
aLambda.plus<A, B>(bLambda)
aLambda.plus<A>(aLambda)
}
и декомпилированный код:
public final class OverloadKt {
public static final void main() {
Function1 aLambda = (Function1)null.INSTANCE;
Function1 bLambda = (Function1)null.INSTANCE;
plus(aLambda, bLambda);
plus(aLambda, aLambda);
}
// $FF: synthetic method
public static void main(String[] var0) {
main();
}
}
, если мы выведем результат этих двух операторов, мы получим:
(T1, T2) -> kotlin.Double
(T1) -> kotlin.Double
Итак, из декомпилированного кода трудно сказать, почему компилятор знает, какой вызов должен быть выполнен, но вызывается правильный оператор!
Но Котлин добавляет кое-что еще к своим файлам - метаданные.
@Metadata(
mv = {1, 1, 15},
bv = {1, 0, 3},
k = 2,
d1 = {"..."},
d2 = {"main", "", "plus", "Lkotlin/Function1;", "T1", "", "f1", "Lkotlin/Function2;", "T2", "f2", "sandbox"}
)
Давайте посмотрим вызовы байт-кода для основного метода:
// access flags 0x19
public final static main()V
L0
LINENUMBER 10 L0
GETSTATIC OverloadKt$main$aLambda$1.INSTANCE : LOverloadKt$main$aLambda$1;
CHECKCAST kotlin/jvm/functions/Function1
ASTORE 0
L1
LINENUMBER 11 L1
GETSTATIC OverloadKt$main$bLambda$1.INSTANCE : LOverloadKt$main$bLambda$1;
CHECKCAST kotlin/jvm/functions/Function1
ASTORE 1
L2
LINENUMBER 13 L2
ALOAD 0
ALOAD 1
INVOKESTATIC OverloadKt.plus (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function2;
POP
L3
LINENUMBER 14 L3
ALOAD 0
ALOAD 0
INVOKESTATIC OverloadKt.plus (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;)Lkotlin/jvm/functions/Function1;
POP
L4
LINENUMBER 15 L4
RETURN
L5
LOCALVARIABLE bLambda Lkotlin/jvm/functions/Function1; L2 L5 1
LOCALVARIABLE aLambda Lkotlin/jvm/functions/Function1; L1 L5 0
MAXSTACK = 2
MAXLOCALS = 2
Итак, в сгенерированном байт-коде ясно, как компилятор определяет, какой метод должен быть вызван. Вот и все. : D