Better

业精于勤荒于嬉

Idiomatic Kotlin: lambda sam constructors

Better's Avatar 2019-06-15 Kotlin

  1. 1. Define and use
  2. 2. Differences of Java and Kotlin Lambda
  3. 3. Compatibity with Java Functional Interface
    1. 3.1. Auto Convert
    2. 3.2. SAM Constructor
    3. 3.3. Java SAM vs. Kotlin Function Type
  4. 4. other
  5. 5. 参考

lambda 表达式( lambda expressions):一段可以传递给其他函数的代码块
高阶函数(high-order-functions):可以接受其他函数作为参数的就是高阶函数
函数式编程(functional programming):一个编程形式,支持函数作为一个普通类型的编程方式。函数可以作为变量,参数。
函数式接口(functional interface):内部只有一个方法的接口。Java 8 的 lambda 只支持这类函数写法。
SAM(signle abstract method):函数式接口的一种叫法,只有一个抽象方法。比如一个方法的接口,一个抽象方法的类。
纯函数(pure function):函数式编程中的一个概念,如果函数不依赖函数外部的变量,那么函数就是纯函数,就是后面说的not capturing。反之就是capturing

在 Java 8 之前通过匿名内部类的方式实现接口回调

1
2
3
4
5
6
button.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent event) {
return false;
}
});

Java 8 中的 lambda 写法

1
button.setOnTouchListener((v, event) -> false);

Define and use

kotlin 中的 lambda 于Java lambda 类似。由{}包裹起来,参数不需要()

1
{ param1 : Type1, param2 : Type2 -> body }
  • 最外部是一个大括号,大括号包裹者代码。
  • 左边是参数,不需要括号,由->区分开
  • 右边是函数体body。如果由返回值用return@<operation>

对于 lambda 的调用方式。可以直接向普通函数一样调用,也可以通过invoke()方法调用,也可以直接的运行,只不过这样可读性不是很好。

1
2
3
4
5
val addOffset = { x: Int -> x + 1 }
// use
println(addOffset(1))
println(addOffset.invoke(1))
println({ x: Int -> x + 1 }(1))

当 lambda 是函数的最后一个参数时,写法上可以变动下,可以直接写在外部

1
2
3
4
5
fun addOffset(init: Int, block: (Int) -> Int): Int = block(init)
fun addOffset(block: (Int) -> Int, init: Int): Int = block(init)
// use
addOffset(1) { x: Int -> x + 1 }
addOffset({ x: Int -> x + 1 }, 1)

由于类型可以被推断,所以可以省略参数的类型

1
2
addOffset(1) { x -> x + 1 }
addOffset({ x -> x + 1 }, 1)

如果参数没有使用,可以用_符号省略

1
addOffset(1) { _ ->  1 }

如果 lambda 的参数只有一个,参数也可以省略,使用it来访问

1
2
addOffset(1) { it + 1 }
addOffset({ it + 1 }, 1)

Differences of Java and Kotlin Lambda

Java 中 lambda 访问外部变量必须为final
Kotlin 中没有这个限制,外部被访问到的变量被翻译成Ref类型,可以传递修改变量。Local Function 中可以看见

Compatibity with Java Functional Interface

Kotlin 中的 lambda 完全兼容 Java 中的 Functional Interface。

Auto Convert

兼容方式一,通常编译器能够将 lambda 表达式自动转换成匿名内部类来实现 Functional Interface 。

  • 对于capturing的函数,Functional Interface 被编译成一个匿名内部类的实列
  • 对于not capturing的函数,Functional Interface 被编译成一个的实列(单列),在每个地方使用
1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 访问了外部的 view
fun testCapturing(context: Context) {
val view = TextView(context)
view.setOnClickListener {
view.text = ""
}
}
// 使用lambda 函数的参数赋值
fun testNotCapturing(context: Context) {
val view = TextView(context)
view.setOnClickListener {
(it as TextView).text = it.javaClass.simpleName
}
}

查看编译的 Java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static final void testCapturing(@NotNull Context context) {
Intrinsics.checkParameterIsNotNull(context, "context");
final TextView view = new TextView(context);
view.setOnClickListener((OnClickListener)(new OnClickListener() {
public final void onClick(View it) {
view.setText((CharSequence)"");
}
}));
}

public static final void testNotCapturing(@NotNull Context context) {
Intrinsics.checkParameterIsNotNull(context, "context");
TextView view = new TextView(context);
view.setOnClickListener((OnClickListener)null.INSTANCE);
}

SAM Constructor

兼容方式二,有时候需要使用 SAM Constructor 可以将lambda 表达式转换成 Functional Interface 的实列。
SAM Constructor 语法

1
FunctionalInterfaceName { lambda_function }

以下情况:

1
2
3
4
5
6
7
// 赋值给变量
val runnable : Runnable = Runnable { print("I am a runnable")}

// 用在返回参数中,返回参数类型被指定了
fun createOnClickListener() : View.OnClickListener {
return View.OnClickListener { v -> print("I am clicked") }
}

请注意,SAM 转换只适用于接口,而不适用于抽象类,即使这些抽象类也只有一个抽象方法。
还要注意,此功能只适用于 Java 互操作;因为 Kotlin 具有合适的函数类型,所以不需要将函数自动转换为 Kotlin 接口的实现,因此不受支持。

Java SAM vs. Kotlin Function Type

Kotlin uses function types instead of interfaces.So if you are coding in pure Kotlin, it is important to use function types for lambda types as Kotlin does not support conversion of lambdas to Kotlin interfaces

在 kotlin 中 function types 定义了函数的规则,只要满足了这种规则的函数都是这一类函数。比如()->Unit是一个 function type,它描述了这样一类函数,不需要参数,返回值是 Void 的类型,比如Runnable接口就符合这种规则。
function type 定义的函数可以通过 SAM Constructor 方式转换成 Java 接口的实现。
function type 不能转换成 kotlin 中的接口实现,所以在定义 lambda 的时候用 function type,可以做更多的兼容。

other

简洁的 concise

Lambda expressions represents these functional interfaces in a more concise way to deal with functional programming

推断 inferred

If the function has a single argument and its type can be inferred, an autogenerated variable named it will be available for you to use

解释 interprets

The compiler interprets a lambda function that represents a functional interface as a instance of an anonymous class implementing that functional interface

参考

kotlinDoc-lambdas
kotlinDoc-sam conversions
Idiomatic Kotlin: lambda and sam constructors

This article was last updated on days ago, and the information described in the article may have changed.