文章目录
概念:Kotlin的标准函数指的是Standard.kt文件中定义的函数,任何kotlin代码都可以自由地调用所有的标准函数。
1. 标准函数with、run 和 apply
1.1 with
- 函数结构:
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return receiver.block()
}
-
参数说明:
with函数接收两个参数:第一个参数可以是一个任意类型的对象,第二个参数是一个Lambda表达式。with函数会在Lambda表达式中提供第一个参数对象的上下文,并使用Lambda中的最后一行代码作为返回值返回。 -
作用:
它可以在连续调用同一个对象的多个方法时让代码变得更加精简。 -
举个栗子:
有一个水果列表,我们想吃完所有水果并将结果打印出来,通常可以这样写:
val list = listOf("apple", "orange", "grape", "banana")
val sb = StringBuilder()
sb.append("Start eating fruits.\n")
for (fruit in list) {
sb.append(fruit).append("\n")
}
sb.append("Ate all fruits.")
print(sb.toString())
使用with精简后的写法:
val list = listOf("apple", "orange", "grape", "banana")
val result = with(StringBuilder()){
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
toString()
}
print(result)
1.2 run
- 函数结构:
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block()
}
-
参数说明:
run函数是一个拓展函数,其用法和使用场景和with是非常类似的,只是语法上稍微做了一些改动而已。首先run函数是不能直接调用的,而是一定要调用某个对象的run函数才行;其次run函数只接收一个Lambda参数,并且会在Lambda中提供调用对象的上下文。其它方面和with是一样的,包括使用Lambda的最后一行代码作为返回值返回。 -
举个栗子:
使用run函数修改一下吃水果的栗子:
val list = listOf("apple", "orange", "grape", "banana")
val result = StringBuilder().run{
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
println("result length $length")
toString()
}
print(result)
T.run就是用扩展函数的方式调用了block: T.(). 所以在T.run函数的代码块中, 可以使用this关键字来得到对主变量T的引用. 在实际编程中, 通过this关键字的调用通常可以不写this… 所以在上面的示例代码中, 我们直接使用了println( l e n g t h ) 而 不 是 p r i n t l n ( length) 而不是println( length)而不是println({this.length})
1.3 apply
- 函数结构:
@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block()
return this
}
-
参数说明:
apply函数和run函数是非常相似的,它也是一个拓展函数,也就是都要在某个对象上调用,并且只接收一个Lambda参数,也会在Lambda中提供调用对象的上下文,但是apply函数无法指定返回值,而是会自动返回对象本身。 -
举个栗子:
使用apply函数修改一下吃水果的栗子:
val list = listOf("apple", "orange", "grape", "banana")
val result = StringBuilder().apply{
append("Start eating fruits.\n")
for (fruit in list) {
append(fruit).append("\n")
}
append("Ate all fruits.")
}
print(result.toString())
实际应用举例:
我们在项目中经常需要在启动某个Activity的时候进行参数传递:
val intent=Intent(context,SecondActivity::class.java)
intent.putExtra("param1","data1")
intent.putExtra("param2","data2")
context.startActivity(intent)
没传递一个参数都要调用一次intent.putExtra(),有多少个参数就得调用多少次,显得有些累赘,如果使用apply函数则可以去掉这种累赘:
val intent=Intent(context,SecondActivity::class.java).apply{
putExtra("param1","data1")
putExtra("param2","data2")
}
context.startActivity(intent)
1.4 let 和also
- 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 {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
return block(this)
}
- also函数结构:
/**
* Calls the specified function [block] with `this` value as its argument and returns `this` value.
*/
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
contract {
callsInPlace(block, InvocationKind.EXACTLY_ONCE)
}
block(this)
return this
}
let和also的函数结构是比较相似的,细微的差别在于各自的返回值不同,T.let返回的是block函数参数的结果,而T.also返回的是this,也就是调用者T本身。
1.5 普通函数with 和 T.run、T.apply等扩展函数的比较
举个栗子:实际项目中我们经常会碰到需要临时改变某个View的属性并改变其显示的状态的需求,现在有一个Checkbox之前被设置为不可点击选中的状态,现在需要改为可点击选中状态并让其选中:
使用with:
fun enable(cb:CheckBox){
with(cb){
cb?.isClickable=true;
cb?.isSelected=true;
}
}
使用run之类的扩展函数:
fun enable(cb:CheckBox){
cb?.run{
isClickable=true;
isSelected=true;
}
}
差别一目了然,就此类应用场景而言,run、apply之类的拓展函数在调用之前就能做好判空操作,更简洁实用
2. 静态方法
2.1 companion object
在java中定义一个静态方法非常简单,只需要在方法上声明一个static关键字即可,通常我们在写工具类的时候都会习惯将里面需要给外部调用的方法定义成static静态方法。那类似的功能到了kotlin中要怎么写呢?
像工具类这种功能,在kotlin中非常推荐使用单例类的方式来实现,比如:
object Util{
fun doSomething(){
println("do something")
}
}
然后就可以调用Util.doSomething()了。
不过,使用单例类的写法会将整个类中的所有方法全部变成类似于静态方法的调用方式,而如果我们只是希望让类中的某个方法变成静态方法的调用方式该怎么办呢?kotlin为我们提供了companion object:
class Util{
fun doSomething1(){
println("do something 1")
}
companion object{
fun doSomething2(){
println("do something 2")
}
}
}
这样就只有doSomething2()变成了类似于静态方法的调用,为什么说是类似于呢?因为其实doSomething2()并不是静态方法,companion object关键字实际上会在Util类的内部创建一个伴生类,而doSomething2()方法就是定义在这个伴生类里面的实例方法。kotlin会保证Util类始终只会存在一个伴生对象,因此调用Util.doSomething2()实际上调用的是Util类中伴生对象的doSomething2()方法。
然而如果我们确实需要定义真正的静态方法,kotlin仍然提供了两种实现方式:注解和顶层方法。
2.2 注解
前面使用的单例类和companion object都只是在语法形式上模仿了静态方法的调用方式,它们都不是真正的静态方法。所以,如果你在java代码中以静态方法的形式去调用的话,你会发现这些方法并不存在,那如果我们想在java中这样调用怎么办呢?kotlin已经为我们考虑到了这种情形,为此,kotlin提供了**@JvmStatic**注解,只要我们给单例类或companion object中的方法加上@JvmStatic注解,那么kotlin编译器就会将这些方法编译成真正的静态方法。如:
class Util{
fun doSomething1(){
println("do something 1")
}
companion object{
@JvmStatic
fun doSomething2(){
println("do something 2")
}
}
}
注意:@JvmStatic注解只能加载单例类或companion object中的方法上,如果加在一个普通方法上,会直接语法报错。
2.3 顶层方法
顶层方法指的是那些没有定义在任何类中方法,比如main()方法。kotlin编译器会将所有的顶层方法全部编译成静态方法。
想要定义一个顶层方法很简单,创建kotlin文件的时候将文件类型选为File即可。我们创建一个File类型的Util.kt文件,内容如下:
fun doSomething(){
println("do something")
}
在kotlin代码中,所有的顶层方法都可以在任何位置被直接调用,不用管包名路径,也不用创建实例。
而java代码没有顶层方法这个概念,所有的方法必须定义在类中,所以是没法直接调用doSomething()这个方法的。那我们要怎么在java代码中调用这种静态方法呢?我们在创建Util.kt之后,kotlin编译器会自动创建一个UtilKt的java类,doSomething()方法以静态方法的形式定义在UtilKt类中,所以在java中使用UtilKt.doSomething()的写法调用即可。