本文主要介绍kotlin的几个特性如下:

  • 默认参数
  • 命名参数
  • std自带函数

继续,如果把编程语言看做一款应用产品,产品的功能对应语言特性,那么大多数编程语言功能都还算齐全,该有的也都有了,剩下一些增增减减,发个版本迭代也是和应用开发类似。这个应用的目的是为了什么呢,为了实现OOP?为了实现函数式编程?

不对,这种思路是本末倒置。就像我们写诗是为了抒发情感,而不是为了拼凑五言绝句平平仄仄平的韵律格式。编程语言设计出来,不是为了实现某个设计哲学,而是要为了被程序员使用。

就像面对 NPE,程序员说:我们不想写if null 判断,语言设计师说:可是添加个 null 实现起来更简单啊,我们加上吧。

你猜,要是编程语言是一个应用产品,这样的产品经理会不会被用户打。

6. 默认参数

Java为了避免在一个方法中传入过多参数,往往有两个解决方法:

  1. 将参数用builder封装一下
  2. 写多个方法重载,每次重载设置最后一个参数为默认值

这里举个方法重载的例子:
下面代码是用Java写的一个路由跳转类的toUriAct方法封装。

public static void toUriAct(Context ctx, String uriStr) {
toUriAct(ctx, uriStr, (HashMap)null);
}

public static void toUriAct(Context ctx, String uriStr, HashMap<String, String> params) {
toUriAct(ctx, uriStr, params, false);
}

public static void toUriAct(Context ctx, String uriStr, HashMap<String, String> params, boolean newTask) {
toUriAct(ctx, uriStr, params, newTask, -1);
}

public static void toUriActWithAnim(Context ctx, String uriStr, int animIn, int animOut) {
toUriAct(ctx, uriStr, (HashMap)null, false, -1, true, animIn, animOut);
}

public static void toUriAct(Context ctx, String uriStr, HashMap<String, String> params, boolean newTask, int requestCode) {
toUriAct(ctx, uriStr, params, newTask, requestCode, false, 0, 0);
}

public static void toUriAct(Context ctx, String uriStr, HashMap<String, String> params, boolean newTask, int requestCode, boolean needAnim, int animIn, int animOut) {
toUriAct(ctx, uriStr, params, newTask, requestCode, needAnim, animIn, animOut, true);
}

public static void toUriAct(Context ctx, String uriStr, HashMap<String, String> params, boolean newTask, int requestCode, boolean needAnim, int animIn, int animOut, boolean useDedaultAnimation) {
toUriActWithReturn(ctx, uriStr, params, newTask, requestCode, needAnim, animIn, animOut, useDedaultAnimation);
}

重复代码多,可读性差。但是,在Java中没得选,只能这么写。

而在kotlin中,只需要写一个方法:

fun toUriAct(ctx: Context, uriStr: String,
params: HashMap<String, String>? = null,
newTask: Boolean = false,
requestCode: Int = -1,
needAnim: Boolean = false,
animIn: Int = 0,
animOut: Int = 0,
useDedaultAnimation: Boolean = false) {
toUriActWithReturn(ctx, uriStr, params, newTask, requestCode, needAnim, animIn, animOut, useDedaultAnimation)
}

kotlin是这么实现默认参数的呢?

kotlin 会为带默认参数的方法生成一个bridge方法,bridge 方法中添加了一个新的int型的mask参数,其值默认为2n-1(n = args.length)。mask的二进制位对应n个参数位。

调用时,如果传参覆盖了默认值,则将参数位对应的mask位置0。

在bridge方法中会检测默认参数的参数位是否置0,否则使用默认值去调用原方法。

// $FF: synthetic method
// $FF: bridge method
public static void toUriAct$default(Context var0, String var1, HashMap var2, boolean var3, int var4, boolean var5, int var6, int var7, int var8, Object var9) {
if((var8 & 1) != 0) {
var0 = (Context)null;
}

if((var8 & 2) != 0) {
var1 = "";
}

if((var8 & 4) != 0) {
var2 = (HashMap)null;
}

if((var8 & 8) != 0) {
var3 = false;
}

if((var8 & 16) != 0) {
var4 = -1;
}

if((var8 & 32) != 0) {
var5 = false;
}

if((var8 & 64) != 0) {
var6 = 0;
}

if((var8 & 128) != 0) {
var7 = 0;
}

toUriAct(var0, var1, var2, var3, var4, var5, var6, var7);
}

但是这种参数带默认值的方法只能在kotlin代码中调用,如果要在Java中调用,只要方法声明前加上@JvmOverloads标注,kotlin就会自动生成上述的方法重载,和用Java写的一样。

7. 命名参数

举例说明:

fun reformat(str: String,
normalizeCase: Boolean = true,
upperCaseFirstLetter: Boolean = true,
divideByCamelHumps: Boolean = false,
wordSeparator: Char = ' ') {
//……
}

调用形式改变如下:

reformat(str, true, true, false, '_')
reformat(str,
normalizeCase = true,
upperCaseFirstLetter = true,
divideByCamelHumps = false,
wordSeparator = '_'
)

Android Studio 3.0 已经自带了 parameter name hints ,所以这条现在看上去算不上特别吸引人了 :)

8. Std自带函数

在kotlin的Standard.kt中定义了一些顶层高阶函数。

TODO、take**的几个顾名思义的方法不提了。下面的run、with、apply、also、let方法命名并不是一个好的实践(动词+名词),我们无法直接从名字上看出这些方法怎么用,怎么区分,只好read code 。

下面一一分析:

  • let
/**
* Calls the specified function [block] with `this` value as its argument and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R = block(this)
  1. receiver的扩展函数
  2. 参数是一个block,block参数为receiver,block内部通过it引用receiver
  3. 返回block的返回值
  4. 常常和安全调用操作符 ?. 配合使用,用来保证let代码块中的it是已经null check过的

demo

val layout = LayoutStyle()
SharedState.previousOrientation?.let { layout.orientation = it }
  • apply
/**
* Calls the specified function [block] with `this` value as its receiver and returns `this` value.
*/
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T { block(); return this }
  1. receiver的扩展函数
  2. 参数是一个block,这个block本身也是receiver的扩展函数,block会在receiver的闭包内执行
  3. 最终返回receiver自身
  4. 常常被用来当做内置的builder,可以方便地实现chain式调用

demo

class LayoutStyle {
var orientation = HORIZONTAL
}

fun main(vararg args: Array<String>) {
val layout = LayoutStyle().apply { orientation = VERTICAL }
}
  • with
/**
* Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R = receiver.block()
  1. with 不是扩展函数
  2. 参数是 receiver 和 block,这个block是receiver的扩展函数,block会在receiver的闭包内执行
  3. 返回block的返回值
  4. 当需要频繁access某个实例时,可以把他包在with代码块中,然后直接访问实例内部变量和方法。

demo

val layout = with(contextWrapper) { 
// `this` is the contextWrapper
LayoutStyle(context, attrs).apply { orientation = VERTICAL }
}
  • run

    run 有两个版本,第一个简单的调用block,第二个带上了泛型信息,成为了扩展函数。

/**
* Calls the specified function [block] and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <R> run(block: () -> R): R = block()
/**
* Calls the specified function [block] with `this` value as its receiver and returns its result.
*/
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R = block()

第一种使用场景不明。。。,这里只看第二种。

  1. receiver的扩展函数
  2. 参数是block,这个block是receiver的扩展函数,block会在receiver的闭包内执行
  3. 返回block的返回值

剩下的also分析省略,总结成表格如下:

function 是否是扩展函数 block闭包环境 返回值
let 是 it block result
run 是 this block result
also 是 it receiver self
apply 是 this receiver self
缺 否 it block result
with 否 this block result
缺 否 it receiver self
缺 否 this receiver self

从上面的表看出,这种function按理应该有 2 3 8种。目前只提供了5种(是否是因为kotlin conf想不到更多的方法名了呢?)。

根据返回值来看:
also 和 apply 返回的 receiver self,方便chain式调用。

let 和 run 返回的是 block result ,可以获取block运行结果。

它们的区别是block内部引用receiver时:

let和also使用it,而run和apply使用this。

从这个角度上看let(带有被动式的操作it)、apply(主动的应用到this)的命名还是比较贴切的。

with作为非扩展函数是单独一档(没有同伴)。with返回 block result,而且 block 使用 this 引用receiver。从主要用法来说——简化对实例内部地访问,命名也很贴切,而且和别的语言惯例也是统一的。

另外在 kotlin.io.Closeable.kt 中还定义了use 扩展函数,也比较有意思。

  • use
/**
* Executes the given [block] function on this resource and then closes it down correctly whether an exception
* is thrown or not.
*
* @param block a function to process this [Closeable] resource.
* @return the result of [block] function invoked on this resource.
*/
@InlineOnly
public inline fun <T : Closeable?, R> T.use(block: (T) -> R): R {
var closed = false
try {
return block(this)
} catch (e: Exception) {
closed = true
try {
this?.close()
} catch (closeException: Exception) {
}
throw e
} finally {
if (!closed) {
this?.close()
}
}
}

use 功能上会自动处理资源关闭的逻辑。有点类似 Java 7 的 try-with-resources 特性。

use 和 let 在函数定义上相似,返回的是 block result,使用it引用receiver。区别是use的receiver 必须是Closeable的子类。

demo 可以看第一篇开头的例子:

fun AssetManager.fileToString(filename: String): String {
return open(filename).use {
it.readBytes().toString(Charset.defaultCharset())
}
}

参考:

Mastering Kotlin standard functions: run, with, let, also and apply