【问题标题】:How to use a predefined lambda in kotlin?如何在 kotlin 中使用预定义的 lambda?
【发布时间】:2021-07-18 14:11:24
【问题描述】:

我正在学习来自 Java 的 Kotlin,我偶然发现了一个意想不到的行为。 我注意到,在我下面的代码中,我似乎不小心在错误的位置声明了一个新的 lambda,而不是 使用 我已经拥有的那个。我该如何解决这个问题?

我写了这两个声明:

/**
 * Dataclass used as an example.
 */
data class Meeple(var name: String, var color: String = "translucent")

/**
 * Function to map from a List<T> to a new List of equal length,
 * containing the ordered elements received by applying a Mapper's map
 * function to every element of the input List.
 *
 * @param T Type of input List-elements
 * @param O Type of output List-elements
 * @param mapper The mapping function applied to every input element.
 * @return The List of output elements received by applying the mapper on all
 * input elements.
 */
fun <T, O> List<T>.map(mapper: (T) -> O?): List<O?> {
    val target = ArrayList<O?>();

    for (t in this) {
        val mapped: O? = mapper.invoke(t)
        target.add(mapped);
    }

    return target;
}

数据类只是一个简单对象的虚拟示例。 List.map 扩展函数旨在从列表的元素映射到新类型并返回该新类型的新列表,就像 Java 中的 Stream.map 一样。

然后我创建一些虚拟的Meeples 并尝试将它们映射到各自的名称:

fun main(args: Array<String>) {

    val meeples = listOf(
        Meeple("Jim", "#fff"),
        Meeple("Cassidy"),
        Meeple("David", "#f00")
    )

    var toFilter: String = "Cassidy"
    val lambda: (Meeple) -> String? =
        { if (it.name == toFilter) null else it.name }
    toFilter = "Jim"

    for (name in meeples.map { lambda }) {
        println(name ?: "[anonymous]") // This outputs "(Meeple) -> kotlin.String?" (x3 because of the loop)
    }
}

我这样做是为了检查 lambda 的行为,以及它稍后是否会过滤“Jim”或“Cassidy”,我的预期是后者,因为那是 lambda 初始化时 toFilter 的状态。

但是我得到了完全不同的结果。 invoke 方法虽然被 IntelliJ 描述为 (T) -&gt; O?,但似乎产生了 lambda 的名称而不是 Meeple 的名称。

看来,对 meeples.map { lambda } 的调用并没有像我预期的那样绑定 lambda,而是创建了一个新的 lambda,它返回 lambda 并可能在内部调用 toString

我将如何实际调用真正的 lambda 方法,而不是声明一个新方法?

【问题讨论】:

  • 以防万一,map 函数存在于 Kotlin 标准库中。
  • @AdamMillerchip 我也很怀疑,尽管这更像是一个可以编写一些 lambda 表达式的现场示例。不过感谢您的提示:)
  • 我想通了。它需要是一个简单的参数,而不是包裹在{ } 中。它应该看起来像这样:for (name in meeples.map(lambda)) {

标签: kotlin


【解决方案1】:

您已经在 cmets 中提到您发现您正在传递一个返回原始 lambda 的新 lambda。

至于toFilter 的值变化: lambda 函数与任何其他接口一样。正如您定义的那样,它捕获toFilter 变量,因此在执行 lambda 时它将始终使用它的当前值。如果您想避免捕获变量,请在定义 lambda 时将其当前值复制到 lambda 中。有多种方法可以做到这一点。一种方法是先将其复制到局部变量中。

var toFilter: String = "Cassidy"
val constantToFilter = toFilter
val lambda: (Meeple) -> String? =
    { if (it.name == constantToFilter) null else it.name }
toFilter = "Jim"

您可以在 Java 中使用 Stream 执行几乎所有操作,您可以直接在 Kotlin 中对 Iterable 执行任何操作。 map 函数已经可用,如 cmets 中所述。


编辑:因为您在 cmets 中提到了 Java 行为。

Java 可以捕获成员变量,但必须将局部变量标记为final,以便编译器允许您将它们传递给 lambda 或接口。因此,从这个意义上说,它们仅捕获值(除非您传递成员变量)。对于 Kotlin 中的局部变量,Java 的 final 等价于 val

在这种情况下,Kotlin 比 Java 更宽松, 允许您将非最终局部变量 (var) 传递给接口或 lambda,并在此捕获变量案子。这就是您的原始代码正在执行的操作。

【讨论】:

  • 是的,这似乎是投票率。作为一名 Java 开发人员,对我来说这是一个有趣的方面,其中 lambdas 捕获值,而不是变量,但这是可以理解的。感谢您对通用接口的回答。
  • 这甚至没有回答问题,不知道你为什么接受它!
  • 我找到了自己的答案,如答案中所述。但我内心的答案对我有很大帮助。
  • 我在回答第二个隐含问题,为什么 lambda 不返回 "Cassidy"
  • @AdamMillerchip 感谢您指出这一点,以便我澄清而不是混淆未来的读者。
【解决方案2】:

即使您发现了您在 cmets 中提到的问题,我仍会在此答案中添加一些详细信息以帮助任何未来的读者。

所以当你使用创建 lambda 时

val lambda: (Meeple) -> String? = { if (it.name == toFilter) null else it.name }

这基本上转化为

final Function1 lambda = (Function1)(new Function1() {
   public Object invoke(Object var1) {
      return this.invoke((Meeple)var1);
   }

   @Nullable
   public final String invoke(@NotNull Meeple it) {
      Intrinsics.checkNotNullParameter(it, "it");
      return Intrinsics.areEqual(it.getName(), (String)toFilter.element) ? null : it.getName();
    }
});

现在将其传递给您的 map 方法的正确方法是您在 cmets 中提到的

name in meeples.map(lambda) 

但是你写的不是(lambda){ lambda },而是trailing lambda convention

name in meeples.map { lambda }  
//  if the last parameter of a function is a function, then a lambda expression passed as the corresponding argument can be placed outside the parentheses:
// If the lambda is the only argument in that call, the parentheses can be omitted entirely

这会创建一个新的 lambda,它会返回我们上面定义的 lambda,这一行基本上会被翻译成下面的代码

HomeFragmentKt.map(meeples, (Function1)(new Function1() {
     public Object invoke(Object var1) {
         return this.invoke((Meeple)var1);
      }

      @Nullable
      public final Function1 invoke(@NotNull Meeple it) {
          Intrinsics.checkNotNullParameter(it, "it");
          return lambda; // It simply returns the lambda you defined, and the code to filter never gets invoked
       }
  }))

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-17
    • 1970-01-01
    • 2017-12-06
    • 1970-01-01
    • 1970-01-01
    • 2021-01-17
    相关资源
    最近更新 更多