Better

业精于勤荒于嬉

Idiomatic Kotlin: Local Function

Better's Avatar 2019-06-08 Kotlin

  1. 1. Define and use
  2. 2. Bummock
  3. 3. Performance overhead
  4. 4. 在Extension 方法中使用
  5. 5. Other
  6. 6. 参考

申明函数是一种复用代码的好方式。

Local Function 就是申明在函数中的函数,嵌套在函数中,官方翻译局部函数。函数通常情况是申明在一个类中。如果只是在局部范围内可以复用,这种时候申明在一个类中,就有一点多余。

Define and use

在 函数中使用fun关键字就可以申明一个嵌套的函数。
下面在登录中验证用户名和密码的有效性

1
2
3
4
5
6
7
8
9
10
11
12
fun login(username: String, password: String) : Boolean {
var something = 1
fun validateInput(input: String){
something++
if (input.isEmpty()) {
throw IllegalArgumentException("Must not be empty")
}
}
validateInput(username)
validateInput(password)
return true
}

Bummock

查看decompile后的 Java 代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public final class LoginKt {
public static final boolean login(@NotNull String username, @NotNull String password) {
Intrinsics.checkParameterIsNotNull(username, "username");
Intrinsics.checkParameterIsNotNull(password, "password");
final IntRef something = new IntRef();
something.element = 1;
<undefinedtype> $fun$validateInput$1 = new Function1() {
// $FF: synthetic method
// $FF: bridge method
public Object invoke(Object var1) {
this.invoke((String)var1);
return Unit.INSTANCE;
}

public final void invoke(@NotNull String input) {
Intrinsics.checkParameterIsNotNull(input, "input");
int var10001 = something.element++;
CharSequence var2 = (CharSequence)input;
boolean var3 = false;
if (var2.length() == 0) {
throw (Throwable)(new IllegalArgumentException("Must not be empty"));
}
}
};
$fun$validateInput$1.invoke(username);
$fun$validateInput$1.invoke(password);
return true;
}
}
  1. 原来的int类型被编译成了IntRef类型。通过将一个非final的变量包裹在Ref类型中,传递或者引用生成的包裹变量,使得可以在其他地方修改原来被包裹变量的值。这种功能叫做capturing
  2. 原来的局部函数被编译成了Function对象,并且通过invoke()调用。这种跟lambda 的实现由异曲同工的效果,lambda 最后也是被编译成Function对象。

Performance overhead

Local Function会存在一点点性能上的问题,虽然微不足道。
新增了一些类对象。比如上面的Ref,以及Function
会增加Dex中方法数量。Function增加了函数个数。kotlin 中inline方法可以避免这种情况,但是local function并不能使用inline标记。

在Extension 方法中使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
data class Credentials(val username: String, val password: String)

fun Credentials.validate() {
fun validateInput(input: String) {
if (input.isEmpty()) {
throw IllegalArgumentException("Must not be empty")
}
}
validateInput(username)
validateInput(password)
}

fun login(credentials: Credentials) {
credentials.validate()
}

创建了Credentials类来封装usernamepassword属性。申明了一个扩展方法validate()来验证属性的合法性,在内部申明了具体的验证操作。如果如果能够确认只会在某些地方才有使用这种验证逻辑,这样做是比较推荐的,不会直接修改Credentials

Other

污染 pollute

This is useful in cases wherein you do not want to pollute the data class with private functions only a the login package will use

垃圾的 littered

However, this approach could easily end you up with a class littered with small functions with no clear relationship between each other..

参考

kotlinDoc-局部函数
Idiomatic Kotlin:local function

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