本文主要介绍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中定义了一些顶层高阶函数。

TODOtake**的几个顾名思义的方法不提了。下面的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时:

letalso使用it,而runapply使用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 特性。

uselet 在函数定义上相似,返回的是 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