Android开发笔记——快速入门(Kotlin与lambda)

[lab.magiconch.com][福音戰士標題生成器]-1727617420599 标准监督

软件环境:

  • Jetbrains Toolbox
  • Android Sudio 2021.1.1 Bumblebee
  • JDK 17.0.2

[TOC]

Kotlin与lambda

Kotlin的集合

在介绍lambda表达式之前先介绍一下kotlin中的集合类,传统意义上的集合主要是list和set还有map。下面来一一介绍一下kotlin中这些内容如何使用。

kotlin的list

list的主要实现类是ArrayList与LinkedList,这里主要是介绍List而不是它的具体实现类

List的特性很简单:

  • 允许出现重复的元素。
  • 元素有序,存入和取出的顺序一致。
  • 元素以一种线性的方式存储,在程序中可以通过索引来访问集合中的主要元素。

使用传统的list像java一样通过函数一个一个初始化添加在kotlin中也是可以的:

val list_test = ArrayList<String>()
list_test.add("apple")
list_test.add("pear")

不过kotlin提供了更加方便的方法,我们使用listof来实现更加方便快捷:

val list_kotlin = listOf<String>("apple","pear")
for (list in list_kotlin)
println(list)

你可以注意到这里声明的list使用的是val关键字而不是var说明他是一个不可变的量,除了初始化添加的内容不允许再添加其他内容。

输出结果如下

apple
pear

kotlin可变的list

kotlin中使用可修改的list也很简单,更换关键字为mutableListOf即可:

var list_var = mutableListOf("apple","pear")
list_var.add("no fruits")
println(list_var[0])
println(list_var[2])

可以看到调用了add来进行添加。

输出结果:

apple
no fruits
kotlin的ArrayList

List与ArrayList的不同点:

List实际上是接口并不是一个普通的类

ArrayList继承了几乎全部的MutableList的方法,除了继承的方法ArrayList类内部还实现了两个独特的方法:

fun trimToSize()
fun ensureCapacity(minCapacity: Int)

分别是可以扩容和缩小占用内存,具体可以见:ArrayList的trimToSize

但是!你如果使用mutableListOf来实现一个可变的列表,它实际上返回的就是ArrayList!

不信可以看源码:

/**
 * Returns a new [MutableList] with the given elements.
 * @sample samples.collections.Collections.Lists.mutableList
 */
public fun <T> mutableListOf(vararg elements: T): MutableList<T> =
    if (elements.size == 0) ArrayList() else ArrayList(ArrayAsCollection(elements, isVarargs = true))

ArrayList是List接口的一个实现类,它是程序中最常见的一种集合。

val list_kotlin = arrayListOf("apple","pear")
for (list in list_kotlin)
    println(list)

它的使用和list基本一致,当然效率相较于list可能会有些下降,因为ArrayList集合在增加或删除指定位置的数据时,会创建新的数组导致效率变低。

kotlin的set

set类与list的不同在于底层,set不允许集合内存在相同的内容,set的集合底层使用的是hash映射机制来存放数据的,因此集合的元素保证了内容的不重复,但同时也失去了顺序。

set的演示:

//set 演示
val set_kotlin = setOf("apple","pear","apple")
for (set in set_kotlin)
    println(set)

可变set演示:

var set_var = mutableSetOf("1","2","3","4")
for (set in set_var)
    println(set)

kotlin的map

map类就不再多说,实际上就是通过键值和内容实现的映射集合,值得注意的是他的迭代方式有所不同:

val map_test = HashMap<String,Int>()
map_test["apple"] = 1
map_test["banana"] = 2
for ((fruit ,number) in map_test)
    println("fruit is $fruit number is $number")

前面介绍过for循环转变为了迭代器,这里就充分发挥了其迭代器的特性,可以同时迭代map中的内容和键值。可以看到输出结果:

fruit is banana number is 2
fruit is apple number is 1

当然kotlin也提供了简洁的写法通过to来实现,键值与内容的映射,但to在这里并不是关键字而实更复杂的内容,我们到后边再说:

val map_kotlin = mapOf("apple" to 1,"banana" to 2)
for ((fruit ,number) in map_kotlin)
    println("fruit is $fruit number is $number")

输出结果:

fruit is apple number is 1
fruit is banana number is 2

可以看到两次输出结果并不一致,这里先挖一个坑,当然你也注意到set并不能直接用【】类似于数组下标的方式访问,这些等以后再详细说明。

kotlin的lambda

集合的函数式API作为lambda表达式的演示再好不过了。

首先我们来说一说什么是lambda。

Lambda就是一小段可以作为参数传递的代码,这就很厉害,因为常规情况下我们作为参数传递的都是变量,而参数转变为lambda表达式的时候就可以传递进去一段带有逻辑处理的代码。

对于一小段这个量词的定义并不明确,通产来说在能保持可读性的前提下可以尽量的短或长。

lambda的常规语法:

{参数名1 :参数类型,参数名2,参数类型 -> 函数体(代码块)}

首先最外层是一个大括号,如果有参数需要向lambda传递的话我们需要先声明参数列表,参数列表的结尾是一个“->”这代表着参数的结束和函数体的开始,函数体的最后一行会自动作为lambda表达式的返回值

什么时候需要传递lambda作为参数?

在Java里面lambda表达式出现所要替代的对象实际上Java的单抽像方法接口也可以被叫做函数式接口,他俩共同的所代表的意义就是:

接口中有且仅有一个抽象方法需要被实现

换句话说就是接口里面只有一个需要被实现的方法。

这种类型的接口作为参数的时候,我们就需要传递一个内部匿名类来实现其抽象方法

这里为了方便下边讲解先说几个结论,并不完全,可自行推广:

  • kotlin的函数式API
  • kotlin的函数式接口
  • Java的函数式接口作为参数

kotlin的闭包

与其说Kotlin是一等公民,不如说是闭包才是一等公民。

闭包在kotlin中常常指的就是由{}构成的lambda表达式,之所以叫他闭包,实际上就是因为他与函数有很大的区别,比如闭包可以访问外部环境的变量,普通函数想要访问外部环境的变量是需要传入参数的,而闭包可以直接访问,并将其保存下来,具体可见参考文章。

参考文章:

https://www.jianshu.com/p/b968524a0e95。

闭包2.

我在这里举一个例子方便大家理解一下,如果看不懂看完本文再回过头来看效果更好:

    val list = listOf("Apple", "Banana", "Orange", "Pear",
        "Grape", "Watermelon")
    val newList = list.map() { fruit: String ->
        {fruit.uppercase() }}

    for (fruit in newList) {
        println(fruit)
    }

这里实际上输出的是闭包或者说是函数类型常量。

kotlin函数式API

我们先说明什么是kotlin函数式API:

先看一段代码,在最开始的一行我们定义了一个lambda表达式,他接受一个String类型的参数并返回字符的长度。

//lambda 表达式演示
val lambda = { fruit:String -> fruit.length}
val Max = list_test.maxOf(lambda)
println(Max)

我们跳转到maxof函数的定义去看看一看它的参数写的是什么:

public inline fun <T, R : Comparable<R>> Iterable<T>.maxOf(selector: (T) -> R): R {
    val iterator = iterator()
    if (!iterator.hasNext()) throw NoSuchElementException()
    var maxValue = selector(iterator.next())
    while (iterator.hasNext()) {
        val v = selector(iterator.next())
        if (maxValue < v) {
            maxValue = v
        }
    }
    return maxValue
}

可以看到它的参数实际上就是一个lambda表达式:selector: (T) -> R

是不是很简单?很明了?我们再来看看什么是kotlin的函数式接口:

kotlin的函数式接口

在kotlin1.4中更新了函数接口,与java不同的是,kotlin需要显示的来定义一个函数接口,在interface接口添加一个fun关键字就可以实现把他转换为函数式接口:

fun interface Eat
{
    fun eat_rice()
}

当函数式接口作为参数的时候就可以使用kotlin的lamda表达式:

//lambda
val lambda2 = { -> println("lambda is ok with kotlin interface")}
test.lambda_tester(lambda2)

因为原方法中并没有参数,所以前边也没有任何参数需要传递,我们可以简写成:

//lambda
val lambda2 = { println("lambda is ok with kotlin interface")}
test.lambda_tester(lambda2)
Java的函数式接口作为参数

如果我们在Kotlin代码中调用一个java方法,并且该方法只接收一个java单抽像方法接口作为参数,我们就可以使用lambda来传递参数:

我们这里来拿java的Thread类来举例子:

thread类的构造方法接收一个Runnable接口作为参数,Runnable是典型的函数式接口,里面只有一个run方法需要实现,当我们使用匿名内部类来实现的时候写法如下:

Thread(object :Runnable
{
    override fun run() {
        println("kotlin is  ok with anonymous")
    }
}).start()

看起来很复杂,这里不再介绍匿名类的具体内容,你只需要知道 kotlin完全舍弃new关键字,这里的object类似于创建一个内部类,先尝试理解一下,实际上和这个感知还是有一定差距,到后面再说,我们直接来说如何用kotlin的lambda来实现java的函数式接口:

Thread(
    Runnable{-> println("kotlin is ok with java")}
		).start()

语法格式:

接口名 { 参数 -> 函数体 }

讲到这里我想你应该明白什么时候用lambda什么参数的情况下使用lambda。

接下来我们顺着kotlin的设计思想,来讨论一下他的语法糖,上边为了你方便理解我并没有使用简写,但kotlin的最关键的还是他的语法糖,语法糖很甜。

kotlin的语法糖

我们回到maxof方法:

//lambda 表达式演示
val lambda = { fruit:String -> fruit.length}
val Max = list_test.maxOf(lambda)

实际上这里的lambda参数可以改写为:

val Max = list_test.maxOf({ fruit:String -> fruit.length})

kotlin规定当lambda参数是函数的最后一个参数的时候可以放到括号外边:

val Max = list_test.maxOf(){ fruit:String -> fruit.length}

接下来如果lambda表达式时函数唯一的参数的话还可以将括号省略:

val Max = list_test.maxOf{ fruit:String -> fruit.length}

不要忘了kotlin具有很强的推导类型的能力,所以参数类型也可以省略:

val Max = list_test.maxOf{ fruit -> fruit.length}

最后如果lambda表达式中只有一个参数的时候可以直接省略参数使用it来替代,同时“->”也可以省略:

val Max = list_test.maxOf{ it.length}

你会对这些省略规则感到复杂,其实抓住重点就是几条:

  • 最后一个参数 ->移出到外边
  • 唯一参数 - > 省略和替代
  • 参数类型- >可推导的省略

我们按照这三个语法糖来实现代码优化:

Java函数式API的语法糖
Thread(object :Runnable
{
    override fun run() {
        println("kotlin is  ok with anonymous")
    }
}).start()

首先Runnable接口是一个java的函数式接口,也就是其中只有一个抽象方法,并且对于Tread来说构造函数也只有这样一个接口参数,这两个唯一就符合上边的省略的替代策略。

省略接口名称,和重写方法的名称:

Thread({
        println("kotlin is ok with java")
    }).start()

同时他也是最后一个参数,也是唯一的参数省略括号和外移:

Thread{
        println("kotlin is ok with java")
    }.start()

牢记语法糖的实现规则,很方便的来优化代码。