【Android】kotlin知识点必知必会

348 阅读4分钟

1.lateinit 和 by lazy

lazyinit和lazy用于在kotlin中延迟初始化,lateinit用于变量var,lazy用于常量val,形如这种形式:

private lateinit var name: String
private val pwd: String by lazy { "123456" }

注意,lazy第一次调用会执行整个lambda表达式,以后调用只返回之前的结果,仔细一品,有点单例的味道,测试代码:

private val pwd: String by lazy {
    println("----- 我执行了")
    "123456"
}

执行:

fun main() {
    println(pwd)
    println(pwd)
}

结果:

lazy.png

2.默认参数

kotlin可以给方法指定默认参数,如果指定了默认参数,调用方法时可以不传入此参数,但其余参数需要指定关键字,未指定参数取默认参数,形如:

fun getStudentInfo(
    name: String = "张三",
    age: Int = 20,
    address: String = "江苏南京",
    sex: String = "男"
): String {
    return "$name,$age,$sex,$address"
}

调用:

fun main() {
    val result = getStudentInfo(age = 19)
    println(result)
}

运行结果:

参数.png

3.data class

kotlin中的数据类,定义非常简单在class前面加个data关键字,然后构造函数中指定变量即可,形如:

data class CityInfo(val name: String, val location: String)

对应于java中:

class CityInfo {

    private String name;
    
    private String location;
    
    public CityInfo(String name, String location) {
        this.name = name;
        this.location = location;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public String getLocation() {
        return location;
    }

    public void setLocation(String location) {
        this.location = location;
    }
}

4.打印日志

kotlin中打印日志采用$关键字,形如:

val name = "张三"
println("你好,我是$name")

val a = 1
val b = 10
println("a+b=${a + b}")

结果:

打印.png

5.单例

简单列举几种写法:

//object修饰的单例
object CityTest

//懒汉式 @Synchronized保证线程安全
class CityTest1 {
    companion object {
        private var instance: CityTest1? = null
            get() {
                if (null == field) {
                    field = CityTest1()
                }
                return field
            }

        @Synchronized
        fun get(): CityTest1 {
            return instance!!
        }
    }
}

//lazy特性,非首次调用只返回结果
class CityTest2 {
    companion object {
        val instance: CityTest2 by lazy(mode = LazyThreadSafetyMode.SYNCHRONIZED) {
            CityTest2()
        }
    }
}

//静态内部类的方式
class CityTest3 {
    companion object {
        val instance = SingletonHolder.holder
    }
    private object SingletonHolder {
        val holder = CityTest3()
    }
}

调用测试:

fun main() {
    println(CityTest)
    println(CityTest)

    println(CityTest1.get())
    println(CityTest1.get())

    println(CityTest2.instance)
    println(CityTest2.instance)

    println(CityTest3.instance)
    println(CityTest3.instance)
}

结果:

单例.png

6.省略findviewById

只需要在build.gradle中添加kotlin-android-extensions插件即可,形如:

plugins {
    ···
    id 'kotlin-android-extensions'
    ···
}

activity中导入import kotlinx.android.synthetic.main.activity_main.*包,然后就可以直接使用xml中的id作为组件的变量名了:

import kotlinx.android.synthetic.main.activity_main.*

class MainActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        //直接使用即可
        btnTest.setOnClickListener {
        }
    }
}

7.扩展函数和扩展属性

kotlin中,扩展函数和扩展属性的含义就是可以给指定类新增属性和方法,比如有些第三方jar中的一些类,我们无法直接修改,可以采用扩展的方式增加属性和方法,方便开发。

下面介绍一下使用方式,其实很简单,类名【.】名字(属性名或者方法名)即可,先看下扩展函数的例子,给Int值新增一个扩展函数,调用后自动加上[],代码如下:

fun Int.toSpString(): String {
    return "[$this]"
}

测试代码:

fun main() {
    println(2.toSpString())
}

结果:

int扩展函数.png

例子非常简单,抛砖引玉,大家可以自行发散思维,扩展函数还是挺方便的,扩展属性也类似,比如:

private val String.upper
    get() = this.toUpperCase()

大小写转换,运行测试:

fun main() {
    println("abc".upper)
}

结果:

大小写.png

注意:对于普通变量,初始化给定的值后将直接写入后端域变量中(一般是field),但扩展属性因为没有后端域变量,所以不应该初始化。

再举个在Android中使用扩展函数的例子:

//比如平时我们加载图片,可以使用扩展函数封装一下,方便使用
fun ImageView.loadImage(url: String) { 
    Glide.with(context).load(url).into(this)
}
//加载图片直接这样用
imageView.loadImage(url)

再比如,dp值转像素值px,此处为float添加扩展属性dp:

//dp值转像素值px
val Float.dp: Float
    get() = android.util.TypedValue.applyDimension(
        android.util.TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics
)

调用:

val width = 20f.dp
Log.e("width","width = $width")

结果:

03-23 10:03:48.069 7594-7594/com.kotlindemo E/width: width = 26.625002

8.Kotlin标准库中的函数

这里盗用下网上的对比图,仅学习用,勿怪,当然推荐大家可以对每个函数进行代码测试,印象更深刻,不然容易混淆。

20200613153033904.png

测试代码如下,重点关注返回值和内部使用关键字(it和this):

fun testMethod() {
    val runResult = 1.run {
        println("run : $this")
        100
    }

    println("runResult : $runResult")

    val applyResult = 1.apply {
        println("apply : $this")
    }

    println("applyResult : $applyResult")

    val alsoResult = 1.also {
        println("also : $it")
    }

    println("alsoResult : $alsoResult")

    val letResult = 1.let {
        println("let : $it")
        100
    }

    println("letResult : $letResult")

    val withResult = with(1) {
        println("with : $this")
        100
    }

    println("withResult : $withResult")
}

打印结果:

内置函数.png

当然除了这几个标准函数之外,还有repeattakeIftakeUnless等,大家可以自行尝试,基本上都是字面意思。

9.Kotlin高阶函数

其实没那么高端,简单来说就是一个可以接收函数作为函数参数的函数,听我这么一说是不是感觉更乱了,不要慌,我们一步步来,我们先定义一个简单的高阶函数:

//把()->Unit 当作是一种函数类型,method就是函数类型的变量名
fun doSomething(method: () -> Unit) {
    method.invoke()
}

()->Unit这个东西表示doSomething方法接收一个无参,无返回值的函数作为自己的参数,invoke()方法就是调用传入的函数,当然你也可以这样调用method(),我们测试一下:

fun main() {
    doSomething {
        println("我是高阶函数的lambda类型的参数")
    }
}

运行:

高阶函数1.png

是不是挺简单的,你可能会说这个doSomething后面怎么直接是个{}啊,其实你也可以这么写:

doSomething(
    fun() {
        println("我是高阶函数的lambda类型的参数")
    }
)

这样可能更容易理解,实际代码还是用lambda推荐的语法,这样写更简便,下面我们在上面的基础上增加参数和返回值,如下:

fun doSomething(param: Int, method: (Int) -> String) {
    val result = method(param)
    println(result)
}

是不是很简单,在()中加入参数类型,->后面是函数的返回值类型,既然我们给高阶函数增加了Int类型的参数,那么调用method方法时这个参数哪里来呢,当然我们主动给它指定就行了啊,所以新增了Int类型的param参数,看下如何使用:

fun main() {
    doSomething(100) {
        val result = it * 2
        return@doSomething "result :$result"
    }
}

传入相关参数即可,这里传入100,做了简单的运算后打印该结果:

高阶函数2.png

下面我们给函数doSomething增加个String类型的返回值,如下:

fun doSomething(param: Int, method: (Int) -> String): String {
    return method(param)
}

fun main() {
    val result = doSomething(100) {
        val result = it * 2
        return@doSomething "result :$result"
    }
    println(result)
}

运行结果和上面一样,我们接收了doSomething方法的返回值并打印:

高阶函数2.png

我要是需要添加两个参数怎么办?那就接着添加呗:

fun doSomething(param: Int, str: String, method: (Int, String) -> String): String {
    return method(param, str)
}

然后调用的时候要注意下,a, b ->这个a代表第一个参数,b代表第二个参数,当然你可以随便起名字都可以的,如果你用不到的参数还可以用_代替:

fun main() {
    val result = doSomething(100, "调用我了") { a, b ->
        val result = a * 2
        return@doSomething "$result -- $b"
    }
    println(result)
}

结果:

高阶函数3.png

我们再进阶下,把所有的参数类型和返回值类型都改为泛型T,试试看:

fun <T> doKotlin(param: T, param2: T, method: (T, T) -> T): T {
    return method(param, param2)
}

我们指定泛型可以代指任何类型,我们调用试试:

fun main() {
    val result = doKotlin(1, 2) { a, b ->
        return@doKotlin a + b
    }
    println(result)
}

结果:

高阶函数4.png

一切正常,那么我们如果要参数类型不同呢,简单,再加个泛型不就行了:

fun <T, R> doKotlin2(param: T, param2: R, method: (T, R) -> R): R {
    return method(param, param2)
}

又加了个泛型R,调用:

fun main() {
    val result = doKotlin2("张三", true) { a, _ ->
        return@doKotlin2 a == "张三"
    }
    println(result)
}

结果:

高阶函数5.png

我们再修改下,上面我们介绍了扩展函数,那么如果我们给泛型加上扩展函数,是不是所有类型都可以使用该高阶函数了呢,试试:

fun <T, R> T.doAndroid(method: (T) -> R): R {
    return method(this)
}

我们定义了泛型T的扩展函数doAndroidmethod接收的参数就是当前类的实例对象,那么我们调用下试试:

fun main() {
    val result = "Hello".doAndroid {
        "100"
    }
    println(result)
}

没啥毛病,那么到这里你是不是有种似曾相识的感觉,如果没有的话,点开kotlin中标准库中的函数,去看看源码,比如let源码:

@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

inline忽略,是不是就是和let源码几乎一致,其实不止let,像applyrun等函数也都是高阶函数的封装,到这里基本上应该可以理解高阶函数的内涵了,代码自己跟着敲下,加深印象,另外熟悉下lambda语法,就基本可以掌握了。

看下apply函数的源码:

@kotlin.internal.InlineOnly
public inline fun <T> T.apply(block: T.() -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block()
    return this
}

发现也是如此,但是你可能会看到T.(),这又是什么鬼呢,既然是T.那么肯定跑不了扩展函数的范畴,但是具体是啥咱也不知道,可以理解为匿名扩展函数,这样在使用的时候在代码块中就可以使用this代指当前对象,这也正是扩展函数所具有的特性:

高阶函数6.png

如果不太理解,可以看看这篇文章

先到这,后续有需要记录的再来补充!