kotlin值得系列1:内置类型。基本类型,数组,Range区间,集合框架,函数,Lambda

1,172 阅读23分钟

本文会开一个系列,本篇为第一篇。


这篇小文,分几点讲。拿来把你。

  • 基本类型
  • 数组
  • Range区间
  • 集合框架(List,Set,Map)(随便说下Pair和Triple)
  • 函数 (核心)

对于有Java基础的卧龙凤雏,核心看下 函数Range集合框架

Range是kt特有的。
函数这点跟Java不大一样。

文章有点长,但是值的一看。不信的话还能怎么办,试试呗。

一、基本类型

Kotlin Vs Java
对比KotlinJava
字节Bytebyte/Byte
整型Int & Longint/Integer & long/Long
浮点型Float & Doublefloat/Float & double/Double
字符Charchar/Character
字符串StringString

基本类型核心点来来来,都在这

Kotlin没有int和long以及byte和char,只有Int和Long,Byte,Char。

  • var是一个可变变量,这是一个可以通过重新分配来更改为另一个值的变量。这种声明变量的方式和java中声明变量的方式一样。

  • val是一个只读变量,这种声明变量的方式相当于java中的final变量。一个val创建的时候必须初始化,因为以后不能被改变。

  • 需要注意的是,kt没有自动类型转换,要转必须手动指定类型。

  • kt的类型转化需要手动指定,方式是 toXXXXXX.

  • 在kt中,== 是比较内容,等价于java的 equals。 而kt中的 === 是比较两者是否为同一个对象的引用。

一.1 变量声明

var 和 val

Kotlin中有两个关键字定义变量,varval

val是只读变量,var是可读写变量。 (话说,我不知道这个l到底代表什么,还不如叫做vao,起码还是个 only)

var是一个可变变量,这是一个可以通过重新分配来更改为另一个值的变量。这种声明变量的方式和java中声明变量的方式一样

val是一个只读变量,这种声明变量的方式相当于java中的final变量。一个val创建的时候必须初始化,因为以后不能被改变。

一.1.1 验证一下var和val

// val 只读
val s1 = "Hello";
s1 = "Hello1" // 想要修改明显报错了

// var 可变变量
var s2 = "ktkt";
s2 = "ktkt 666" // var 明显可以修改

image.png

一.1.1.2 完整的变量声明和 自动类型推导

val s1: String ="Hello";

(感觉很Java不太一样是吧,类型写到后面去了,别急,有得救)

但是你如果像下面这么写,也没错。

val s1 = "Hello";

这个过程,叫做 自动类型推导

为了懒惰和舒服,我们当然更多的是使用 自动类型推导。

一.1.1.3 Kotlin的数值类型转化

kt的类型转化需要手动指定,方式是 toXXXXXX.

// 这样当然没问题,我们没手动指定类型
var a = 10;
var b =a; 

var a1: Int = 6;
// 这样就会报错,kt不存在自动类型转化
// var b1: Float = a1;  

// 这样就没问题,手动转化类型了
var b1: Float = a1.toFloat();

一.1.1.4 Kotlin的中的 == 和 ===

在kt中,== 是比较内容,等价于java的 equals。
而kt中的 === 是比较两者是否为同一个对象的引用,或者说比较地址。

var s1 = "hello";
var s2 = "hello";
// 如果使用构造函数创建新的String
// 则会明确告诉Kotlin我们想要一个新的对象。因此,将创建一个新的String并将其放在堆上
var s3 = String("hello".toCharArray())

println(s1==s2); // true
println(s1===s2);// true
println(s1==s3);// true
println(s1===s3);// false

一.1.1.5 字符串模板

  • 字符串可以包含模板表达式 ,即一些小段代码,会求值并把结果合并到字符串中。 模板表达式以美元符($)开头,由一个简单的名字构成,或者用花括号扩起来的任意表达式。

  • 注意下 trimIndent()trimMargin() .
    .

  • 模板表达式以美元符 $ 开头,由一个简单的名字构成:

val i = 10
println("i = $i") // 输出“i = 10”
  • 或者用花括号 ${}括起来的任意表达式:
val s = "abc"
println("$s.length is ${s.length}") // 输出“abc.length is 3”
  • 原始字符串与转义字符串内部都支持模板。
    如果你需要在原始字符串中表示字面值 $ 字符(它不支持反斜杠转义),你可以用下列语法:
val price = """
${'$'}9.99
"""
  • trimIndent()用于切割每一行开头相同数量的空格。
  • trimMargin()函数用于从字符串中每行的开头裁剪指定的字符串参数以及前面的全部空格,如果不提供参数,则以|作为参数默认值。
fun main() {
    //原始字符串:使用三个双引号包起来的字符串,原始字符串中的\将不被识别为转义,字符串中可以包含换行,如:
    fun getText1(): String {
        return """        Kotlin is created by JetBrains
        Kotlin is a Programming Language running on JVM
        We can use Kotlin to create Android apps"""
    }

    // 可以使用trimMargin()、trimIndent()裁剪函数来去除前导空格。

    //trimIndent()用于切割每一行开头相同数量的空格。
    fun getText2(): String {
        return """        Kotlin is created by JetBrains
        Kotlin is a Programming Language running on JVM
        We can use Kotlin to create Android apps""".trimIndent()
    }

    //trimMargin()函数用于从字符串中每行的开头裁剪指定的字符串参数以及前面的全部空格,如果不提供参数,则以|作为参数默认值。
    fun getText3(): String {
        return """Kotlin is created by JetBrains
        #Kotlin is a Programming language running on JVM
        #We can use Kotlin to create Android apps""".trimMargin("#")
    }

    println(getText1())
    println(getText2())
    println(getText3())
}

输出:

        Kotlin is created by JetBrains
        Kotlin is a Programming Language running on JVM
        We can use Kotlin to create Android apps
        
Kotlin is created by JetBrains
Kotlin is a Programming Language running on JVM
We can use Kotlin to create Android apps

Kotlin is created by JetBrains
Kotlin is a Programming language running on JVM
We can use Kotlin to create Android apps

.
.
.

二、数组

数组是IntString等类似数据类型的集合。数组在Kotlin中使用Array类来表示。Kotlin中的数组本质上是可变的,具有固定大小,这意味着可以对数组元素执行读写操作。

集合类型 Collection.Kotlin 中的集合类型分为3种集合:

  • 有序可重复: Array -- 数组
  • 有序不重复: Set
  • 无序不重复: Map

数组的创建

数组的构造函数.
使用指定的大小和init函数声明数组构造函数。 init函数用于返回带有索引的数组元素。

Array(size: Int, init: (Int) -> T)

如何创建呢?

可以使用arrayOf()intArrayOf()charArrayOf()booleanArrayOf()longArrayOf()shortArrayOf()byteArrayOf()函数创建Kotlin数组。

其实这么多方法,也就分为两类,arrayOf 和 其他方法。arrayOf创建的,数组值可以是不同类型,而比如intArrayOf(创建基本类型数组),这些,创建出来的数组类型是指定类型的。

我们看个下面的代码就知道。 image.png

但是,通过 arrayOf<T> 也是可以指定类型的。比如 arrayOf 。来,上栗子

var myArray1 = arrayOf(1,10,4,6,15)  
var myArray2 = arrayOf<Int>(1,10,4,6,15)  
val myArray3 = arrayOf<String>("Ajax","Prake","Michel","John","Curry")  
var myArray4= arrayOf(1,10,4, "Ajax","Prake")

其他创建 arrayOfNulls、 emptyArray

如果我们想像 Java 中那样创建一个长度指定,每个值都是 null 的数组,就要用 kotlin.arrayOfNulls()  函数

如果我们需要一个空的数组,Kotlin 提供了 kotlin.ArrayIntrinsics.emptyArray()  函数

数组的设置和获取 set和get

可以get和set,也可以直接 实例名[x] 直接设置或者取值

fun main() {
    var myArray1 = arrayOf(1,2,3,4)
    println(myArray1.joinToString())
    myArray1.set(1,100)
    myArray1[3]= 400
    println("set一下: ${myArray1.joinToString()}")

    println("myArray1 get1: ${myArray1.get(1)}")
    println("myArray1 get2: ${myArray1[2]}")
}

输出

1, 2, 3, 4
set一下: 1, 100, 3, 400
myArray1 get1: 100
myArray1 get2: 3
a, b, c, d

数组的长度 size

kt获取长度基本都是size方法。

数组的遍历

上代码,

  • 第1种方式 for in
  • 第2种方式 for in indices
  • 第3种方式 forEach
  • 第4种方式 forEachIndexed
fun main() {
    //5个长度的数组,分别是index * 3,这个的it代表索引index,系统默认it代表索引
    val array: Array<String> = Array(5) { (it * 3).toString() }

    println("第1种方式 for in")
    for (item in array) {
        println(item)
    }

    println("第2种方式 for in indices")
    // kotlin数组提供了一个indices属性,这个属性可返回数组的索引区间
    // 这种通过索引区间遍历的实现具有更好的性能,kotlin将会在底层将其编译成根据内存地址来访问元素,不需要额外创建区间对象
    for (index in array.indices) {
        println("$index->${array[index]}")
    }

    println("第3种方式 forEach")
    // 这里的it也代表了索引
    array.forEach { println(it) }

    println("第4种方式 forEachIndexed")
    array.forEachIndexed { index, item -> println("$index->$item") }
}

.
.
输出:

1种方式 for in
0
3
6
9
122种方式 for in indices
0->0
1->3
2->6
3->9
4->123种方式 forEach
0
3
6
9
124种方式 forEachIndexed
0->0
1->3
2->6
3->9
4->12

数组查找

fun main() {
    val numbers = arrayOf(0, 66, 99, 18, 78, 11)
    if(66 in numbers){
        println("包含66") // 输出
    }else{
        println("不包含66")
    }

    if(numbers.contains(18)){
        println("包含18") // 输出
    }else{
        println("不包含18")
    }

}

数组的一些其他内置函数

  • (1) 检查包含是否某个元素: contains方法
  • (2) 丢弃元素系列:dropdropWhiledropLastdropLastWhile
  • (3) 查找模式: find 查询的是第一个元素
  • (4) 把整个数组中的元素中间加逗号输出:joinToString, 当然也可以加以定制
  • (5) 取出元素系列:taketakeLastdropdropLast
  • (6) 切割数组:sliceArray
  • (7) 反转数组:reverse
  • (8) 数组排序:sortedArraysortedArrayDescending, 加强版排序:sortedBysortedByDescending
  • (9) 数组变形: map -- 可以把一种数组转换成另一种类型
  • (10) 筛选器: filter
  • (11) 最大和最小: maxmin

三、Range 区间

这个java没有,kotlin特有

就是数学上那个区间。

  • 闭区间 ..
  • 左闭右开 until
  • 降序区间 downTo
  • 区间的步长 step

闭区间 .. 包含起止值

    var intRange=1..5
    println(intRange.joinToString())


    var charRange='a'..'z'
    println(charRange.joinToString())
    
输出
1, 2, 3, 4, 5
a, b, c, d, e, f, g, h, i, j, k, l, m, n, o, p, q, r, s, t, u, v, w, x, y, z

左闭右开 until 包含开始值,不包含结束值

fun main() {
    val iRange = 1 until 10//[1,10)
    println(iRange.joinToString())
}

输出
1, 2, 3, 4, 5, 6, 7, 8, 9

降序区间 downTo

rangeTo  //升序区间
downTo()  //降序区间
reversed()  //翻转区间
fun main() {
    val intRange = 10 downTo 1//[10,1]
    println(intRange.joinToString())
}


输出
10, 9, 8, 7, 6, 5, 4, 3, 2, 1

区间的步长 step

fun main() {
    val intRangeWithStep = 1..10 step 2//[1,10]步长为2 输出结果:1,3,5,7,9
    // 1
    // 1+2(步长)
    // 1+2+2
    println(intRangeWithStep.joinToString())
}

输出
1, 3, 5, 7, 9

四、集合框架

先上个图吧。

image.png

简单来说吧,集合的类型分为ListSetMap三种类型的,但是Kotlin中相对特殊的是,集合是分为可变的不可变的

注意数组Array不是集合类型的

  • List 是一个有序集合,可通过索引(反映元素位置的整数)访问元素。元素可以在 list 中出现多次。列表的一个示例是一句话:有一组字、这些字的顺序很重要并且字可以重复

  • Set 是唯一元素的集合。它反映了集合(set)的数学抽象:一组无重复的对象。一般来说 set 中元素的顺序并不重要。例如,字母表是字母的集合(set)。

  • Map(或者字典)是一组键值对键是唯一的,每个键都刚好映射到一个值。值可以重复。map 对于存储对象之间的逻辑连接非常有用,例如,员工的 ID 与员工的位置。

为了更好地理解集合,下面做个解释:


  • Iterable 父类任何类继承这个接口就表示可以遍历序列元素
  • MutableIteIterable 在迭代器期间支持删除元素的迭代
  • Collocation List和Set的父类接口(Map不是),只可读不可变
  • MutableCollection 支持添加和删除元素的Collection。它提供了写入的函数,如add、remove或clear等

  • List 最常用的集合,继承Collection接口,元素有序,只读不可变
  • MutableList 继承List,之处添加和删除元素,除了拥有List中读取的函数,还有add、remove或clear等写入数据的函数

  • Set 元素无重复、无序。继承Collection接口。只读不可变
  • MutableSet 继承Set,支持添加和删除元素Set

  • Map 存储K-V对的集合,在Map映射表中Key是唯一的。
  • MutableMap 支持添加和删除元素的Map

我们知道kotlin也是基于jvm的,在集合框架这一块,kt本身和java几乎一致,只是提供了丰富的api和添加了不可变的类型

四.1 创建集合类

Kotlin中分别用 listOf()setOf()mapOf()  创建不可变的容器,使用mutableListOf()  、mutableSetOf()  、mutableMapOf()  创建可变的Mutable容器

image.png

image.png

image.png

show me you code

四.1 .1 创建带元素的集合

fun main() {
    val list = listOf(1, 2, 3, 4, 5)//创建不可变List
    // list.add 因为不可变这里根本add不出来
    // list.remove 因为不可变这里根本remove不出来

    val mutableList = mutableListOf(1, 2, 3, 4, 5)//创建可变mutableList
    mutableList.add(5,66) // 这里是可以add的
    mutableList.removeAt(0)

    println("list输出: ${list.joinToString()}")
    println("mutableList输出: ${mutableList.joinToString()}")

    // 关于add和remove,set和map的不可变同理
    val set = setOf(1,2,3,4,5)//创建不可变Set
    val mutableSet = mutableSetOf(1,2,3,4,5)//创建可变mutableSet
    
    // 这里需要注意,map的键值对之间,使用to来间隔的
    val map = mapOf(1 to "A" , 2 to "B" ,3 to "C")//创建不可变Map
    val mutableMap = mutableMapOf(1 to "A" , 2 to "B" ,3 to "C")//创建可变mutableMap
}

注意一下map的元素复制的to

输出:

list输出: 1, 2, 3, 4, 5
mutableList输出: 2, 3, 4, 5, 66

四.1 .2 创建不带元素的集合

  • 如果创建没有元素的空List使用ListOf()即可。不过需要显式指定变量的类型。否则会报错
val list = listOf<Int>()
val set = setOf<String>()
val map = mapOf<Int,String>()

四.2 集合的遍历

  • forEach实现遍历 (list,set,map都可以)
  • forEachIndexed 实现遍历遍历下标和值 (只有list,set 可以,map可以通过entries实现)
  • map独有的 entries.forEach

forEach

  • List、Set集合继承了Iterable接口,里面扩展了forEach函数来迭代遍历元素。同样Map接口也扩展了forEach函数迭代遍历元素。

forEach实现遍历

fun main() {
    val list = listOf<Int>(1, 2, 3, 4, 5, 6)
    list.forEach { println(it) }
    
    println("===========")
    val set = setOf<String>("A", "B", "C")
    set.forEach { println(it) }
    
    println("===========")
    val map = mapOf<Int, String>(1 to "o", 2 to "p", 3 to "q")
    map.forEach { println("key = ${it.key} value = ${it.value}") }
}

输出

1
2
3
4
5
6
===========
A
B
C
===========
key = 1 value = o
key = 2 value = p
key = 3 value = q

forEachIndexed 和 entries.forEach

forEachIndexed 实现遍历遍历下标和值

如果我们想在迭代器遍历的时候获取index下标,在List和Set中可以使用下面的forEachIndexed函数

fun main() {
    val list = listOf<Int>(1, 2, 3, 4, 5, 6)
    //第一个参数是index 索引,第二个参数 i 是value
    list.forEachIndexed { index, i -> println("index = ${index} value = ${i}") }
    println("======")
    val set = setOf<String>("A", "B", "C")
    set.forEachIndexed { index, s -> println("index = ${index} value = ${s}") }

    // 注意:map 没有 forEachIndexed 方法
}

输出

index = 0 value = 1
index = 1 value = 2
index = 2 value = 3
index = 3 value = 4
index = 4 value = 5
index = 5 value = 6
======
index = 0 value = A
index = 1 value = B
index = 2 value = C

map独有的 entries.forEach

Map的元素是Entry的,可以通过Entry属性获取Map所有键值对的Set

fun main() {
    val map = mapOf(1 to "o", 2 to "p", 3 to "q")
    map.entries.forEach { println("key = ${it.key} , value = ${it.value}") }
}

输出

key = 1 , value = o
key = 2 , value = p
key = 3 , value = q

四.3 映射函数

  • map: 返回一个每一个元素根据给定的函数转换所组成的List。
  • flattern: flatten 处理嵌套集合,遍历这个嵌套集合,将每一个子集合中的元素通过addAll方法添加到新的集合中,最终得到一个扁平化的集合
  • flatMap:遍历所有的元素,为每一个创建一个集合,最后把所有的集合放在一个集合中。(相当于map加上flattern)

map函数

看看map函数

public inline fun <T, R> Iterable<T>.map(transform: (T) -> R): List<R> {
    return mapTo(ArrayList<R>(collectionSizeOrDefault(10)), transform)
}

我们能看到,最后返回的都是List。

list,set,map使用 map函数的示例

fun main() {
    println("list=======")
    val list = listOf(1, 2, 3, 4, 5, 6)
    val mapForList = list.map { it * it }// map函数对每个元素进行乘方操作
    println(mapForList)

    println("set=======")
    val set = setOf("A", "B", "C")
    val mapForSet = set.map { it + "helloMap" }//map函数对每个元素进行字符串拼接
    println(mapForSet)
    println("map=======")
    val map = mapOf(1 to "o", 2 to "p", 3 to "q")
    val mapForMap = map.map { it.value + "Map" }
    println(mapForMap)
}

输出:

list=======
[1, 4, 9, 16, 25, 36]
set=======
[AhelloMap, BhelloMap, ChelloMap]
map=======
[oMap, pMap, qMap]

flatten 函数

flatten 处理嵌套集合,遍历这个嵌套集合,将每一个子集合中的元素通过addAll方法添加到新的集合中,最终得到一个扁平化的集合 (flatten本身就是变平,扁平的意思)

来个例子吧

fun main() {
    val list = listOf(1, 2, 3)
    val mapFList = list.map { it -> listOf(it + 100, it + 200, it + 300) }.flatten()
    println(mapFList)
}

输出

[101, 201, 301, 102, 202, 302, 103, 203, 303]

好好看看这个图片 image.png

相当于:
1+100,1+200,1+300
接着
2+100,2+200,3+300
……

类似于两个for

flatMap 函数

看看kt对这个 flatMap 是怎么写的

/**
 * Returns a single list of all elements yielded from results of [transform] function being invoked on each element of original collection.
 */
public inline fun <T, R> Iterable<T>.flatMap(transform: (T) -> Iterable<R>): List<R> {
    return flatMapTo(ArrayList<R>(), transform)
}

public inline fun <T, R, C : MutableCollection<in R>> Iterable<T>.flatMapTo(destination: C, transform: (T) -> Iterable<R>): C {
    for (element in this) {
        val list = transform(element)
        destination.addAll(list)
    }
    return destination
}

.
.
不想看源代码就直接看这里

flatMap 遍历集合中的元素,然后将每个元素传入transform中处理后得到一个列表,将这个列表的所有元素添加到destination中,最终得到一个扁平化的列表

flatMap:遍历所有的元素,为每一个创建一个集合,最后把所有的集合放在一个集合中。(相当于map加上flattern)

如果仅仅是对一个集合进行扁平化操作时,使用flatten就可以了。 如果需要对其中的元素进行一些“加工”,可以考虑使用flatMap

fun main() {
    val list = listOf(1, 2, 3)
    val mapFList =  list.flatMap { it -> listOf(it + 100, it + 200,it + 300) }
    println(mapFList)
}

输出

[101, 201, 301, 102, 202, 302, 103, 203, 303]

是不是觉得flatMap和flatten很像呢,其实,基本是的。

xxx.flatMap { it.items }xxx.map { it.items }.flatten()在效果上是等价的。

四.4、过滤,排序,去重

  • 过滤: filter filterIndexed
  • 排序 倒叙reversed 升序sorted 降序sortedDescending
  • 去重 distinct

四.4.1 过滤

filter 常用的过滤方式

data class Student(var name: String, var age: Int, var score: Int) {
    override fun toString(): String {
        return "$name ${age}${score}分"
    }
}
fun main() {
    val list = listOf(Student("张三", 18, 80)
        , Student("小明", 16, 70)
        , Student("小李", 16, 50)
        , Student("小王", 20, 100)
        , Student("小陆", 18, 59))
    // filter 过滤 (小于60分的学生)
    val filter = list.filter { it.score < 60 }
    println(filter)
}

输出

[小李 16岁 50分, 小陆 18岁 59分]

filterIndexed 结合下标进行过滤


data class Student(var name: String, var age: Int, var score: Int) {
    override fun toString(): String {
        return "$name ${age}${score}分"
    }
}

fun main() {
    val list = listOf(
        Student("张三", 18, 80),
        Student("小明", 16, 70),
        Student("小李", 16, 50),
        Student("小王", 20, 100),
        Student("小陆", 18, 59)
    )
    val filter1 = list.filterIndexed { index, student -> index > 2  && student.score > 55 }
    println(filter1.joinToString())
    //输出  小王 20岁 100分, 小陆 18岁 59分
    
    val filter2 = list.filterIndexed { index, student -> index % 2 == 0 && index > 3 }
    println(filter2.joinToString())
    //输出 [小陆 18岁 59分]
}

输出

小王 20岁 100分, 小陆 18岁 59分
小陆 18岁 59分

四.4.2 排序 倒叙reversed 升序sorted

  • 倒叙reversed
  • 升序sorted
  • 降序sortedDescending
fun main() {
    // 倒叙 reversed()
    val list = listOf(1, 2, 3)
    val set = setOf(4, 5, 6)
    println(list.reversed())
    println(set.reversed())

    // 升序 sorted()
    // (sorted()也是直接调用了java api来实现的)
    val listSort = mutableListOf(300, 200, 100)
    val setSort = setOf(600, 500, 400)
    println(listSort.sorted())
    println(setSort.sorted())

}

输出

[3, 2, 1]
[6, 5, 4]
[100, 200, 300]
[400, 500, 600]

四.4.3 去重 distinct

fun main() {
    val list = listOf(6, 2, 2, 2, 3, 4, 3, 1)
    // distinct 去重
    println(list.distinct())

    // 结合使用一下
    // 先去重后排序
    println(list.distinct().sorted())
    // 先去重后排序,再倒叙
    println(list.distinct().sorted().reversed())
}

输出

[6, 2, 3, 4, 1]
[1, 2, 3, 4, 6]
[6, 4, 3, 2, 1]

四.5、集合的其他常见方法

  • groupBy: 返回一个根据给定函数分组后的map。
  • mapIndexed: 返回一个每一个元素根据给定的包含元素index的函数转换所组成的List。
  • mapNotNull: 返回一个每一个非null元素根据给定的函数转换所组成的List。

具体例子可以参考 这个链接

四.6、元组 Pair 和 Triple

元组 可以把多个值同时赋给一个变量,或者同时给多个变量赋值。kotlin中元组分为二元元组(Pair)和三元元组(Triple),在新版Kotlin中已经删除了多元元组。也就是只有Pair和Triple。

元组没什么好说的,来个例子的

fun main() {
    val student1 = Triple<String, String, Int>("Alfred", "男", 29)
    val student2 = Pair<String,Int>("Thomas",21)
    val student3 = Pair<String,String>("jack","男")
    
    println(student1.first)
    println(student1.second)
    println(student1.third)
    
    println("========")
    println(student2.first)
    println(student2.second)
    
    println("========")
    println(student3.first)
    println(student3.second)
}

输出


Alfred
男
29
========
Thomas
21
========
jack
男

Pair可以通过 to 的形式来创建值的内容。

其实元组这种东西吧,不管是Tair还是Triple,使用场景都相对少,往好了说,有的元素有限,不一定定义一个类,比如学生成绩,只有名字和成绩,但是你怎么能保证以后就一定就不需要拓展呢。所以使用前要考虑清楚。

简单来说,个人感觉有两个作用吧

  • 1、替换简单的对象
  • 2、作为返回值,实现多返回。

但是能不用我肯定不用,感觉后面改动的可能性比较大。

五、函数

函数这一块,还是很重要的。

  • 了解完基本函数
  • 接着是匿名函数(Lambda,SAM转换)
  • 最后是高阶函数 (常见高阶函数,函数式编程)

kt的函数,不管怎么说,都不开这个 定义 形式。只不过有的东西可以省略罢了。 image.png

  • 函数的定义一定需要 fun 关键字
  • Unit相当于java里面的void,无返回值的时可以省略。
  • 函数的返回值是放在后面的,这点和java不一样

举个简单的函数例子:

fun getValue(v: Int): Int { 
    return v
}

五.1 带默认参数的函数

java的重载函数太多,kotlin在声明函数时候,可以指定参数的默认值,这样可以指定参数默认值,避免创建重载参数。

kt函数在定义时可以指定参数默认值,但是定义时带默认值的参数最后放在后面,避免歧义

fun main() {
    //age的默认参数是18
    fun showStu1(name: String ,score: Int,age: Int =18){
        println("名字:$name ,年龄:$age, 分数:$score")
    }
    // age默认参数是18。而且定义时放在了之后,调用时可以不写
    showStu1("王哈哈",100)

    //也可以这么写,但是不建议,这些写调用时需要指定 参数名 = 参数值
    // 默认值的参数最后放在后面,避免歧义
    fun showStu2(name: String ,age: Int =18,score: Int){
        println("名字:$name ,年龄:$age, 分数:$score")
    }
    // 需要指定参数名和参数值
    showStu2("张哈哈",score = 60)
}

输出

名字:王哈哈 ,年龄:18, 分数:100
名字:张哈哈 ,年龄:18, 分数:60

.
.

这里需要注意一点,子类继承父类的参数默认值函数后,是不允许重写的函数为其参数指定默认值。好在这种情况编译器会提示错误。

open class FatherClass {
    open fun setValue(x: Int, y: Int = 10, z: Double = 1.2) = x + y + z
}

class SonClass: FatherClass() {
    //  像这样,子类想复写了父类的方法,但是修改默认参数值,就会报错
    //  as也会提示的
    override fun setValue(x: Int, y: Int, z: Double = 18) = x + y + z
}

image.png

.
.

单表达式函数

如果函数体只是单个表达式时,可以省略花括号并用"=" 指定代码体

fun main() {
    // showFun1 和 showFun2 是等价的
    fun showFun1(x: Int, y: Int) {
        println(x + y)
    }
    // showFun2 的函数体只是单个表达式,所以用 = 来直接表示了
    fun showFun2(x: Int, y: Int) = println(x + y)
}

.
.

五.2 不定长参数函数 vararg (也叫 可变数量参数)

有很多场景函数的变量的个数是不确定。Java是通过三个点"..."表示不定个数的参数。而Kotlin需要通过关键字vararg定义参数,表示函数的参数个数不确定。

fun main() {
    fun foo(vararg args: String) {
        println(args.contentToString())
    }
    foo("王哈哈","今天","累了")
}

输出

[王哈哈, 今天, 累了]

五.3 尾递归函数 tailrec

Kotlin支持尾递归的编程风格。允许一些算法可以通过循环而不是递归解决问题,避免堆栈溢出导致的系统不稳定。Kotlin还提供了尾递归优化的关键字tailrec。但要符合 tailrec 修饰符的条件,需要函数必须将其自身调用作为它执行的最后一个操作。我们用求阶乘的代码演示尾递归。

fun main() {
    // 尾递归,可以保证堆栈不溢出,但是还要考虑数据类型的取值范围
    tailrec fun tailrecTest(num: Int, result: Int): Int {
        println("剩余递归次数 : $num \t 计算结果: $result")
        return if (num == 0) {
            1
        } else {
            tailrecTest(num - 1, result + num)
        }
    }

    tailrecTest(10,100)
}

输出

剩余递归次数 : 10 	 计算结果: 100
剩余递归次数 : 9 	 计算结果: 110
剩余递归次数 : 8 	 计算结果: 119
剩余递归次数 : 7 	 计算结果: 127
剩余递归次数 : 6 	 计算结果: 134
剩余递归次数 : 5 	 计算结果: 140
剩余递归次数 : 4 	 计算结果: 145
剩余递归次数 : 3 	 计算结果: 149
剩余递归次数 : 2 	 计算结果: 152
剩余递归次数 : 1 	 计算结果: 154
剩余递归次数 : 0 	 计算结果: 155

五.4 Lamdba 表达式

来一个lambda

val sum123 = { x:Int, y:Int -> 
    x + y
} 

这是一段关于Lambda表达式的代码。是吧,两个数相加。我们也践行了上述的3个特征。

Lamdba表达式这个家伙,其实可以完全另起一篇文章的。 但是算了,还是在这里说吧,文章长一点就长一点吧。

记住:

  • 在Kotlin中,函数作为一等公民存在,函数可以像值一样被传递。lambda就是将一小段代码封装成匿名函数,以参数值的方式传递到函数中,供函数使用。

  • Lambda表达式的本质其实是匿名函数,底层还是通过匿名函数来实现。Lambda的出现确实是减少了代码量,同时代码变得更加简洁明了。

本质是匿名函数,匿名函数,匿名函数,重要的事说 3 次。

既然说到了匿名函数,多少还是来点代码吧

fun main() {
    // 定义一个普通函数,函数名为 funC1
    fun funC1() {
        println("一个普通函数")
    }
    funC1();

    // 定义一一个匿名函数
    // 注意这里funCCC只是一个变量名
    var funCCC = fun() {
        println("一个匿名函数")
    }
    funCCC() // 通过变量名funCCC接收,调用匿名函数
    
    // Lambda表达式
    val lamTest = { println("一个Lambda表达式") }
}


输出
一个普通函数
一个匿名函数

记下来记下来记下来
通过lambda的几个特性,还有上面的代码,我认为初次入门的童鞋,可以这么简单理解:

我们都说 lambda 本质就是一个匿名函数,但是特别之处在于,lambda表达式连 fun 关键字都不用,可一定得有一对{} 包裹,但是如果没有入参,连 -> 都可以不要


穿插一下 函数的类型

要知道,Kotlin中函数类型作为函数的返回类型

Lambda表达式是匿名函数,匿名函数也是函数,匿名函数的函数类型和普通函数并无二致。

函数的类型,之的就是函数的返回类型。

可以通过AS 的 shift+ctrl + p ,查看函数的类型

还是来点代码吧

image.png

image.png

image.png

穿插一下 -> 操作符作用

在kotlin中,一定要知道,-> 并不是lambda的专有的

->一般用于3个作用

1、分离lambda表达式的参数和主体

val sum = { x: Int, y: Int -> x + y }

2、在函数类型中分隔参数和返回类型声明

(R, T) -> R
或者
(INT)-> String

3、分离when表达式分支的条件和主体

when (x) {
    0, 1 -> print("x == 0 or x == 1")
    else -> print("otherwise")
} 

五.4.1 Lambda 表达式的调用方式

一共2种。

lambda的调用有两种方式,一种是通过()来进行调用,另一种是通过invoke()函数进行调用,两种方式没有区别。

fun main() {
    val lambda = { println("test") }
    lambda() // 第一种调用方式
    lambda.invoke()  // 第二种调用方式
}




输出

test
test

五.4.2 Lambda的3个特征

  • 1、Lambda表达式总是被大括号括着
  • 2、其参数(如果存在,不存在可省略)在 -> 之前声明(参数类型可以省略)
  • 3、函数体在 -> 后面。(lambda表达式返回值,总是返回函数体内部最后一行表达式的值) (如果labmda表示 -> 左边没有参数,那么 -> 可以省略) 记住他!记住他!记住他!
fun main() {
    // 一个普通函数
    fun sum(){
        println("一个普通的函数")
    }
    sum()

    // 一个相对完整的 lambda表达式, -> 左边有参数的
    var sum1 = { x:Int, y:Int ->
        x + y
    }
    println(sum1(2,3))

    // lambda 表达式,没有参数的
    val sum2 = {
        println(" -> 左边不存在参数的lambda,那么连 -> 都可以省略")
    }
    sum2()
}
一个普通的函数
5
 -> 左边不存在参数的lambda,那么连 -> 都可以省略

五.4.3 Lambda 的常见用法

通过Lambda表达式的3个常见用法,来更好的认识Lambda表达式。

  • 无参数的 Lambda 表达式
  • 有参数的 Lambda 表达式
  • lambda表达式作为函数中的参数的时候

五.4.3.1 无参数的 Lambda 表达式

无参数形式为:

val 函数名 = { 函数体 }

fun main() {
    // 采用lambda
    val hello = { println("hello kotlin001") }
    hello()

    // 等价于函数
    fun hello() {
        println("hello kotlin002")
    }
}

输出

hello kotlin001

五.4.3.2 有参数的 Lambda 表达式

  1. 完整表达方式:

val 函数名 : (参数1类型, 参数2类型, ...) -> 返回值类型 = { 参数1, 参数2, ... -> 函数体 }

  1. 表达式返回值类型可自动推断形式

val 函数名 = { 参数1:类型1, 参数2:类型2, ... -> 函数体 }

来个例子

fun main() {
    // lambda 表达式
    val sum1: (Int, Int) -> Int = { a, b -> a + b }
    // (lambda 表达式)
    val sum2 = { a: Int, b: Int -> a + b }
    // sum1 等价于 sum2 ,他们都是lambda表达式

    // 普通函数 不采用labmda版本
    fun sum(a: Int, b: Int): Int {
        return a + b
    }
}

五.4.3.3 lambda作为函数中的参数和返回值

lambda作为参数
fun main() {
    fun twoAndThree(operation: (Int, Int) -> Int) {
        val result = operation(2, 3)
        println("The result is $result")
    }

    twoAndThree { a, b -> a + b }
    twoAndThree { a, b -> a * b }
}
作为返回值
fun main() {
    fun getSum(): (Int, Int) -> Int {
        return { a, b -> a + b }
    }

    val sum = getSum()(1, 2)
    println("sum的值 $sum "  )
}

输出

sum的值 3 

其实说到了这里,就不得不提一下高阶函数。 lambda 的应用场景就是高阶函数,我们可以把一个 lambda 当做参数传递到高阶函数中,获取返回一个 lambda。

准确的来说,高阶函数就是以另一个函数作为参数或者返回值的函数。

关于高阶函数的东西,我们会另外再说。