当第一个参数是 class 时,无法用 lambda 替换 SAM 构造函数

Can't replace SAM-constructor with lambda when first argument is a class with one method

我对 SAM 构造函数感到困惑,我有这个 Java class:

public class TestSam<T> {

    public void observe(ZeroMethods zero, Observer<T> observer) {
    }

    public void observe(OneMethod one, Observer<T> observer) {
    }

    public void observe(TwoMethods two, Observer<T> observer) {
    }

    public interface Observer<T> {
        void onChanged(@Nullable T t);
    }

    public interface ZeroMethods {
    }

    public interface OneMethod {
        First getFirst();
    }

    public interface TwoMethods {
        First getFirst();

        Second getSecond();
    }

    public interface First {
    }

    public interface Second {
    }
}

还有这个 Kotlin 代码:

fun testSam(
        test: TestSam<String>,
        zero: TestSam.ZeroMethods,
        one: TestSam.OneMethod,
        two: TestSam.TwoMethods
) {
    test.observe(zero) { println("onChanged $it") } // 1. compiles
    test.observe(zero, TestSam.Observer { println("onChanged $it") }) // 2. Redundant SAM-constructor

    test.observe(one) { println("onChanged $it") } // 3. doesn't compile
    test.observe({ one.first }) { println("onChanged $it") } // 4. compiles
    test.observe(one, TestSam.Observer { println("onChanged $it") }) // 5. compiles

    test.observe(two) { println("onChanged $it") } // 6. compiles
    test.observe(two, TestSam.Observer { println("onChanged $it") }) // 7. Redundant SAM-constructor
}

这是怎么回事?为什么 Kotlin 不能找出 3.(并提供特殊变体 4.),但处理所有其他情况?


此代码的基本原理是 Android 中的 LiveData<T>.observe(LifecycleOwner owner, Observer<T> observer) 方法,其中 LifecycleOwner 有一个方法 getLifecycle()

我在编译器中发现了一个规则:如果Java-方法调用需要类型是 SAM 接口,那么你可以用 lambdas(或函数)替换它们,但是 所有个这样的参数,或none个参数。

所以,你有方法:public void observe(OneMethod one, Observer<T> observer)。 这两个参数都是 SAM 候选者。您可以拨打:
observer(object1, object2)
或:
observer(function1, function2)

不是:
observer(object1, function2)
不是:
observer(function1, object2)

即使在 3 个或更多参数的情况下,也会出现相同的行为。 原因是编译器设计的技术难度。

对不起,如果我不是很清楚,我的英语不是很好。

这将在 Kotlin 1.4 中修复,请参阅 https://blog.jetbrains.com/kotlin/2020/03/kotlin-1-4-m1-released/

Kotlin has supported SAM conversions for Java interfaces from the beginning, but there was one case that wasn’t supported, which was sometimes annoying when working with existing Java libraries. If you called a Java method that took two SAM interfaces as parameters, both arguments need to be either lambdas or regular objects. It wasn’t possible to pass one argument as a lambda and another as an object. The new algorithm fixes this issue, and you can pass a lambda instead of a SAM interface in any case, which is the way you’d naturally expect it to work.


随着即将推出的新类型推断,此问题将在 Kotlin 编译器中得到修复。现在可以在 Android 项目中启用实验类型推断(需要 Kotlin 1.3),方法是将其添加到模块级 gradle 文件:

tasks.withType(org.jetbrains.kotlin.gradle.tasks.KotlinCompile).all {
    kotlinOptions {
        freeCompilerArgs = ["-XXLanguage:+NewInference"]
    }
}

应该可以启用它(但 it doesn't quite work yet):

kotlin {
    experimental {
        newInference = "enable"
    }
}

旁观者写道:

The cause of this is technical difficulty in compiler design

在我看来,Kotlin 编译器不想为具有符合 SAM 转换条件的 n 参数的方法生成 2^n 变体,因此它只生成两个变体: 一个全是 lambda,一个没有 lambda

YouTrack 上有一个相关问题:Impossible to pass not all SAM argument as function