美文网首页
kotlin<第四篇>:基础(2)

kotlin<第四篇>:基础(2)

作者: NoBugException | 来源:发表于2022-08-09 01:26 被阅读0次
一、判空
fun doStudy(study: Study) { 
    study.readBooks() 
    study.doHomework() 
}

Kotlin默认所有的参数和变量都不可为空,所以这里传入的Study参数也一定不会为空。
Kotlin将空指针异常的检查提前到了编译时期。

那如果我们的业务逻辑就是需要某个参数或者变量为空该怎么办呢?

fun doStudy(study: Study) {
    study.readBooks()
    study.doHomework()
}

study 默认不可以为空,但是如果在 Study 后面添加  ? 呢?

fun doStudy(study: Study?) {

}

添加 ? 之后,表明 study 可以为空,如下图所示:
image.png
由于 study 可能为空,所以报错,这时需要处理 study 为空的所有可能才不会报错,这样就避免了 Kotlin 空指针异常的可能。

修改后的代码如下:

fun doStudy(study: Study?) {
    if (study == null){
        return
    }
    study.readBooks()
    study.doHomework()
}

if 语句可能会增加代码的复杂度,可以改成如下代码:

fun doStudy(study: Study?) {
    study?.readBooks()
    study?.doHomework()
}

当 study 为空时,则什么都不做,当 study 不为空时,则调用。

// 当 a 不为空,则返回a,否则返回b
val c = a ?: b 

// 这是一种有风险的写法,意在告诉Kotlin,我非常确信这里的对象不会为空,
// 所以不用你来帮我做空指针检查了,如果出现问题,你可以直接抛出空指针异常,后果由我自己承担。
val upperCase = content!!.toUpperCase() 

let 和 ?.结合使用:

fun doStudy(study: Study?) {
    study?.let { stu ->
        stu.readBooks()
        stu.doHomework()
    }
}

fun doStudy(study: Study?) {
    study?.let {
        it.readBooks()
        it.doHomework()
    }
}

此外,let函数是可以处理全局变量的判空问题的,而 if 判断语句则无法做到这一点。
二、let、with、run、apply、also
let 前面已经说过了,主要是和 `?.` 结合使用;

with 有两个参数,第一个参数可以是一个任意类型的对象,第二个参数是一个Lambda 表达式。

原有代码:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val builder = StringBuilder()
builder.append("Start eating fruits.\n")
for (fruit in list) {
    builder.append(fruit).append("\n")
}
builder.append("Ate all fruits.")
val result = builder.toString()
println(result)

改进后的代码:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = with(StringBuilder()){
    // 闭包中的上下文就是 StringBuilder
    append("Start eating fruits.\n")
    for (fruit in list) {
        append(fruit).append("\n")
    }
    append("Ate all fruits.")
    toString()
}
println(result)

run 和 with 功能上差不多,只是语法存在差异:

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().run{
    // 闭包中的上下文就是 StringBuilder
    append("Start eating fruits.\n")
    for (fruit in list) {
        append(fruit).append("\n")
    }
    append("Ate all fruits.")
    toString()
}
println(result)

apply 语法和 run 一样,apply 无法指定返回值类型,它的返回值只能是对象本身

val list = listOf("Apple", "Banana", "Orange", "Pear", "Grape")
val result = StringBuilder().apply{
    // 闭包中的上下文就是 StringBuilder
    append("Start eating fruits.\n")
    for (fruit in list) {
        append(fruit).append("\n")
    }
    append("Ate all fruits.")
}
println(result.toString())

在实际开发中如何选择?可以根据需要的返回值类型选择,然后再区分闭包中持有的变量来选择。
【1】返回值是本身的有:
         apply(闭包持有的变量是this,info本身)
         also(闭包持有的变量是it,info本身)
【2】返回值是最后一行代码的有:
         let(闭包持有的变量是it,info本身)
         run(闭包持有的变量是this,info本身)
         with(闭包持有的变量是this,info本身)

run 和 with 并没有太大的区别,只是写法稍微不一致,with比较常用。

it 和 this 的使用场景?
it:一般使用集合操作的场景
this:一般使用对象的操作,可以修改对象的属性
三、单例

kotlin 单例实现:

object Util { 
    fun doAction() { 
         println("do action") 
    } 
}
四、静态方法
Kotlin 中就非常推荐使用单例类的方式来实现

object Util { 
    fun doAction() { 
         println("do action") 
    } 
}

不过,使用单例类的写法会将整个类中的所有方法全部变成类似于静态方法的调用方式,
而如果我们只是希望让类中的某一个方法变成静态方法的调用方式该怎么办呢?
使用 companion object 可以解决这个问题

class Util {

    fun doAction1() {
        println("do action1")
    }

    // doAction2()方法其实也并不是静态方法,companion object这个关键字实际上会 在Util类的内部创建一个伴生类
    companion object {
        fun doAction2() {
            println("do action2")
        }
    }
}

val u = Util();
u.doAction1()
Util.doAction2()

不管是单例方法还是伴生类方法,Kotlin 编译器并不会将那些方法编译成静态方法,
但是,如果在方法前面添加注解 @JvmStatic 之后,Kotlin 编译器就会编译成静态方法。

companion object {
    @JvmStatic
    fun doAction2() {
        println("do action2")
    }
}

还有一种方式是:顶层方法
顶层方法:不在类中的方法,在Kotlin项目的任何地方都可以调用顶层方法;
Kotlin 编译器会将所有的顶层方法全部编译成静态方法;

假设在 Test.kt 文件中添加 doSomething 方法,在 kt 文件的任何位置直接调用即可:

    doSomething()

如果是Java文件,调用方式如下:

    TestKt.doSomething();
五、大括号可以省略
当函数体只有一句代码时,大括号可以省略

fun print() = println("Helo Kotlin")
fun largerNumber(num1: Int, num2: Int): Int = max(num1, num2)
fun largerNumber(num1: Int, num2: Int) = max(num1, num2) 
六、多次执行
    repeat(2) {

    }
七、?、!!、?:、::、===
"?"加在变量名后,系统在任何情况不会报它的空指针异常。
"!!"加在变量名后,如果对象为null,那么系统一定会报异常。
"?:"对象A ?: 对象B 表达式,意思为,当对象 A值为 null 时,那么它就会返回后面的对象 B
"::"把一个方法当做一个参数,传递到另一个方法中进行使用,通俗的来讲就是引用一个方法
在Kotlin中,=== 表示比较对象地址,== 表示比较两个值大小
八、泛型
泛型类:

class MyClass<T> {
    fun method(param: T): T {
        return param
    }
}

val myClass = MyClass<Int>()
val result = myClass.method(123)


泛型方法:

class MyClass {
    fun <T> method(param: T): T {
        return param
    }
}

val myClass = MyClass()
val result = myClass.method<Int>(123)

对泛型的类型进行约束:

class MyClass<T : Number> {
    fun  method(param: T): T {
        return param
    }
}

class MyClass {
    fun <T : Number> method(param: T): T {
        return param
    }
}

泛型在高级函数中的应用:

fun StringBuilder.build(block: StringBuilder.() -> Unit): StringBuilder {
    block()
    return this
}

StringBuilder.() 中的 StringBuilder 指定了具体的上下文类型。

sb.build {
    append("A")
    append("B")
    append("C")
}

以上代码闭包中的上下文就是 StringBuilder,那么,结合泛型,闭包中的上下文类型是可以改成泛型的:

fun <T> T.build(block: T.() -> Unit): T {
    block()
    return this
}
九、运算符重载
class Money(val value : Int) {

    operator fun plus(money: Money) = Money(money.value + value)

    operator fun plus(newValue: Int) = Money(newValue + value)
}

operator fun plus 是重载+的固定写法,不能改变

val money = Money(10) + Money(20)
val money2 = Money(10) + 1
println(money.value)
println(money2.value)

以上代码是重载+的实现方法,剩下的参考下表即可:
1656427756044.png
如果是非自定义类,比如SDK自带的String,那么可以结合扩展函数用法,将重载运算符的方法添加到类中:

operator fun String.times(n: Int): String {
    val builder = StringBuilder()
    repeat(n) {
        builder.append(this)
    }
    return builder.toString()
}

val str= "abc" * 3
println("str:$str")

另外,必须说明的是,其实Kotlin 的String类中已经提供了一个用于将字符串重复n遍的repeat()函数,
因此times()函数还可以进一步精简成如下形式:

operator fun String.times(n: Int) = repeat(n)
十、类型转换
使用 as 进行类型转换,但是 as 是不安全的,有可能会因为不能成功转换而发生crash,代码如下:

val y=null
val x:String = y as String

解决方案是:使用安全的类型转换 as?

val y=null
val x: String? = y as? String
十一、耗时计算
// 毫秒
val time1 = measureTimeMillis {

}

// 纳秒
val time2 = measureNanoTime {

}
十二、vararg
vararg关键字,它允许方法接收任意多个同等类型的参数,相当于Java的可变长参数(...)。

    fun max(vararg nums: Int): Int {
    var maxNum = Int.MIN_VALUE
    for (num in nums) {
        maxNum = kotlin.math.max(maxNum, num)
    }
    return maxNum
}

val a = 10
val b = 15
val c = 5
val d = 20
val largest = max(a, b, c, d)

以上代码是由缺陷的,就是这个方法指定了 int 类型,不能比较其他整型,所以需要进行改造:

fun <T: Comparable<T>> max(vararg nums: T): T {
    if (nums.isEmpty())
        throw RuntimeException("Params can not be empty.")
    var maxNum = nums[0]
    for (num in nums) {
        if (num > maxNum) {
            maxNum = num
        }
    }
    return maxNum
}

在 Java 中,如果想要实现所有数据类型都可以比较,必须实现 Comparable 接口, Kotlin 同样成立。
十三、继承 Parcelable
java语言可以直接继承Parcelable,然而Kotlin需要添加插件:
id 'kotlin-android-extensions' 
或 
id 'kotlin-parcelize' 
或
id 'org.jetbrains.kotlin.android.extensions'

然后如下:

@Parcelize
class DemoEvent(val content: String) : Parcelable

[本章完...]

相关文章

网友评论

      本文标题:kotlin<第四篇>:基础(2)

      本文链接:https://www.haomeiwen.com/subject/hrmwwrtx.html