Kotlin 学习笔记(四)—— 作用域函数、inline 关键字、反引号等 Kotlin 基本用法(下)

简介: Kotlin 学习笔记(四)—— 作用域函数、inline 关键字、反引号等 Kotlin 基本用法(下)

4. Kotlin 对象比较


在 Java 中,要比较两个对象是否相等,通常用的是 == 或 equals 方法。Java 中的 == 运算符比较的是两个对象本身的值,即两个对象在内存中的首地址。如果是两个字符串,就是比较的这两个字符串存储的地址是否是同一个。

Java 中,对象的首地址是它在内存中存放的起始地址,它后面的地址是用来存放它所包含的各个属性的地址,所以内存中会用多个内存块来存放对象的各个属性值,而通过这个首地址就可以找到该对象,进而可以找到该对象的各个属性。

Java 中的 equals 方法比较的是两个对象中各个属性值的是否相同。如果是两个字符串,就是比较的两字符串所包含的内容是否相同。

在 Kotlin 语言中,判断两个对象是否相等用的是 == 和 ===。没错,两个等号和三个等号。Kotlin 的 == 相当于 Java 中的 equals 方法;而 === 相当于 Java 中的 == 运算符,记住即可。栗子也有,看下方:

// code 8
val str1 = java.lang.String("我发")
val str2 = java.lang.String("我发")
println("str1 == str2 结果为 ${str1 == str2}") // 输出:str1 == str2 结果为 true
println("str1 === str2 结果为 ${str1 === str2}") // 输出:str1 === str2 结果为 false

因为在 Kotlin 的 String 构造方法中,不能直接传入一个字符串,所以这里用的是 Java 中的 String 类进行的初始化。也可以用 Kotlin 的 String 另外两种初始化方法:1)val str1 = StringBuilder("我发").toString();2)val str1 = String("我发".toByteArray())。


5. Kotlin 的常量变量


根据笔记一中的内容,我们知道,Kotlin 有两种变量,一种是用 val 关键字修饰的不可变的变量;另一种是用 var 关键字修饰的可变的变量。如何在类中对这两种变量进行初始化呢?val 因为是不可变,所以只能重写变量的 getter 方法,var 则可以重写 getter 和 setter 方法,当然类会自动帮我们生成 getter 和 setter 方法。

// code 9
class Person {    // 此类无实际意义,为了举个栗子而已
    var age: Int = 0 // 可变变量我们可以重写 getter 和 setter
        get() {
            return field.plus(10)
        }
        set(value) {
            field = value - 1
        }
    val name: String = "" // 不可变变量我们只能重写 getter 方法
        get() {
            return field + "haha"
        }
    val height: Float // height 是用 val 修饰的,但 height 并不是一个常量
        get() {
            return (age * 2F + 10)
        }
}

在重写 getter 和 setter 方法时,可以通过 field 拿到该属性的值。val 和 var 最本质的区别就是,val 没有 setter 方法。val 并不是常量,而是说,我们不能再次对 val 变量进行赋值操作! 为啥 val 修饰的并不是常量?可以看一下 code 9 中的 height 变量,当 age 变化时,height 也会变化,它并不是一个常量。

如果要声明一个常量,则要用到 const 关键字。它有两个注意点: 1)const 只能修饰 object 的属性,或 top-level 变量。 2)const 变量的值必须在编译期间就确定下来,所以类型只能是 String 或基本数据类型。

啥意思呢?我理解的就是,Kotlin 中用 const 修饰的常量类似于 Java 中的一个不可变的静态变量。它声明的地方只有三种:

  1. object 类的内部,object 修饰的都是静态类;
  2. top-level 位置,也就是在一个类的外部进行声明;
  3. companion object 内部,也就是用于声明静态变量的位置。
// code 10
object ValAndVarExample {
    const val t2 = "heiheihei"
}
const val t1 = "hahaha" // top-level,类外
class Person {
    companion object{
        const val t3 = "hehehe"
    }
}


6. Kotlin 的 inline、crossinline、noinline 关键字的特殊使用


在前面的第2节 Kotlin 的自定义操作符中,已经说明了 inline 关键字的基本用法,知道了内联函数可以通过直接将代码拷贝到调用的地方从而加快程序执行速度的特性。除了 inline 关键字外,还有 crossinline 和 noinline 两个关键字,来看看它们还有什么特殊的用法。

在讲之前,还是需要明白一些前提知识。inline 关键字既会影响函数对象本身,也会影响传入的 Lambda 闭包参数,两者都会被内联到调用点。

编译预处理器会对内联函数进行扩展,省去了参数压栈、生成汇编语言的 CALL 调用、返回参数、执行 return 等过程,从而提高运行速度。优点是,在函数被内联后编译器可以通过上下文相关的优化技术对结果代码执行更深入的优化;但会使得编译后的代码体积变大,只是省去了函数调用的开销。所以 inline 适合用于较简单的频繁调用的函数。


6.1. 被 inline 修饰的函数中的 Lambda 表达式,可以中断外部函数的调用。

啥意思?没关系,大家都是一脸懵。得结合例子说一下子:

// code 11
fun main(args: Array<String>) {
    test1 {
        println("我要好好学 Kotlin")
        return
    }
    println("我要好好学习 Android")
}
inline fun test1(lambda: () -> Unit) {
    lambda.invoke()
}
// 输出:我要好好学 Kotlin

test1 函数被 inline 修饰,它有个 Lambda 闭包,在该闭包中有个 return 返回函数,这个函数可以中断外部的 main 函数,所以只会输出 “我要好好学 Kotlin”。

通常情况下,Kotlin 中函数内部 Lambda 闭包是不能中断外部函数的执行的,可以尝试下将 code 11 中 test1 修饰的 inline 去掉,此时编译器就会提示 return 只能写成 return@test1,即只能返回 test1 函数,并不能返回 main 函数。


6.2. crossinline 关键字不允许被 inline 修饰的函数中的 Lambda 表达式中断外部函数的执行。

意思就是,在 code 11 中,如果 Lambda 表达式的 return 只是想中断该闭包的执行,而不想中断外部 main 函数的执行,该咋办?有人会说,那我不用 inline 不就可以了?但这里又需要用 inline 呢?那就可以使用 crossinline 去修饰这个 Lambda 闭包,编译器就不会去对这个 Lambda 表达式做内联操作。

// code 12
fun main(args: Array<String>) {
    test1 {
        println("我要好好学 Kotlin")
        return@test1
        println("我不想学习了~")
    }
    println("我要好好学习 Android")
} 
inline fun test1(crossinline lambda: () -> Unit) {
    lambda.invoke()
}
// 输出:
//我要好好学 Kotlin
//我要好好学习 Android


6.3. noinline 关键字不允许被 inline 修饰的函数中的 Lambda 表达式被内联处理。

首先,noinline 关键字是作用于 Lambda 闭包的;其次,它是用于在修饰了 inline 关键字的函数中,剔除 inline 关键字对 Lambda 闭包的影响,让它就作为一个普通的 Lambda 闭包。说明不够,代码来凑!

// code 13
inline fun test2(lambda0: () -> Unit, noinline lambda1: () -> Unit): () -> Unit {
    lambda0.invoke()
    lambda1.invoke()
    return lambda1
}


test2 函数被 inline 修饰,有两个 Lambda 闭包作为参数,而且它的返回值也是一个 Lambda 闭包。如果 lambda1 没有 noinline 关键字修饰,那么它就会跟 lambda0 一样,将函数体直接拷贝到调用的地方,这种情况下,lambda1 就不能作为闭包返回了,所以去掉 noinline 之后,code 13 代码会报错。所以,这里如果要将 test2 用 inline 修饰,同时,又想返回一个闭包的话,就可以用 noinline 关键字去除 inline 对闭包的影响。

上面说的都是关于 inline 关键字的进阶用法,通常情况下不会用到,作为知识储备即可。

这篇笔记就到此为止了,更多 Kotlin 学习笔记可以查看:

Kotlin 学习笔记(一)

Kotlin 学习笔记(二)

Kotlin 学习笔记(三)—— Kotlin 的动态代理你会写吗?

还没看够?欢迎来我的公众号转转~


参考文献

  1. 张涛;极客时间 Kotlin 系列课程
  2. 深山里的小白羊;《内联函数》 blog.csdn.ne/qq_33757398…
  3. 韦邦杠;《java中equals以及==的用法(简单介绍)》 www.cnblogs.com/weibanggang…
  4. One_Month;《Kotlin中的noinline》 blog.csdn.net/One_Month/a…
  5. DONGYUXIA;《Kotlin基础之内联函数》 blog.chinaunix.net/uid-3147827…

ps. 赠人玫瑰,手留余香。欢迎转发分享加关注,你的认可是我继续创作的精神源泉。

目录
相关文章
|
1天前
|
Kotlin
Kotlin中的函数定义
Kotlin中的函数定义
10 4
|
1天前
|
Kotlin
Kotlin中的函数分类(顶层、成员、局部、递归等)
Kotlin中的函数分类(顶层、成员、局部、递归等)
7 1
|
3天前
|
Kotlin
Kotlin函数
Kotlin函数
6 0
|
1月前
|
Kotlin
Kotlin - 标准函数(with、run和apply)
Kotlin - 标准函数(with、run和apply)
11 1
|
8月前
|
缓存 API Android开发
Kotlin 学习笔记(七)—— Flow 数据流学习实践指北(三)冷流转热流以及代码实例(下)
Kotlin 学习笔记(七)—— Flow 数据流学习实践指北(三)冷流转热流以及代码实例(下)
94 0
|
8月前
|
缓存 Java Kotlin
Kotlin 学习笔记(七)—— Flow 数据流学习实践指北(三)冷流转热流以及代码实例(上)
Kotlin 学习笔记(七)—— Flow 数据流学习实践指北(三)冷流转热流以及代码实例(上)
71 0
|
8月前
|
存储 缓存 Android开发
Kotlin 学习笔记(六)—— Flow 数据流学习实践指北(二)StateFlow 与 SharedFlow(下)
Kotlin 学习笔记(六)—— Flow 数据流学习实践指北(二)StateFlow 与 SharedFlow(下)
118 0
|
8月前
|
存储 缓存 人工智能
Kotlin 学习笔记(六)—— Flow 数据流学习实践指北(二)StateFlow 与 SharedFlow(上)
Kotlin 学习笔记(六)—— Flow 数据流学习实践指北(二)StateFlow 与 SharedFlow(上)
58 0
|
8月前
|
API Android开发 Kotlin
Kotlin 学习笔记(五)—— Flow 数据流学习实践指北(一)(下)
Kotlin 学习笔记(五)—— Flow 数据流学习实践指北(一)(下)
34 0
|
8月前
|
安全 Kotlin
Kotlin 学习笔记(五)—— Flow 数据流学习实践指北(一)(上)
Kotlin 学习笔记(五)—— Flow 数据流学习实践指北(一)(上)
50 0