Please enable Javascript to view the contents

Kotlin

 ·  ☕ 20 分钟  ·  🎅 qqnv

异常

Kotlin标准库提供了一些便利函数,使用这些内置函数,你可以拋出带自定义信息的异常,这些便利函数叫做先决条件函数,你可以用它定义先决条件,条件必须满足,目标代码才能执行。

函数 描述
checkNotNull 如果参数为null,则抛出llegalStateException异常,否则返回非null值
require 如果参数为false,则抛出llegalArgumentException异常
requireNotNull 如果参数为null,则抛出llegalStateException异常,否则返回非null值
error 如果参数为null,则抛出llgalStateException异常并输出错误信息,否则返回非null值
assert 如果参数为false,则抛出AssertError异常,并打上断言编译器标记
1
2
3
4
5
fun check(number: Int?) {
  checkNotNull(number,{"Something is wrong"})
  number ?: throw UnskilledException()
}
class UnskilledException : IllegalArgumentException("操作不当")

字符串操作

substring

字符串截取,substring函数支持IntRange类型(表示一个整数范围的类型)的参数,until创建的范围不包括上限值。

1
2
3
4
5
6
7
const val NAME = "Jack's friend."
fun main(){
  val index = NAME.indexof('\'')
  //NAME.substring(0,index)
  val str = NAME.substring(0 until index)
  println(str)
}

split

split函数返回的是List集合数据,List集合又支持解构语法特性,它允许你在一个表达式里给多个变量赋值,解构常用来简化变量的赋值。

1
2
3
4
5
6
7
const val NAME = "Jack,Rose,Merry"
fun main(){
  //val data = NAME.split(',')
  //data[0]
  val (origin, dest, proxy) = NAME.split(',')
  println("$origin $dest $proxy")
}

replace

字符串替换

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
fun main(){
  //字符串替换
  val str = "The people's Republic of China"
  //第一个参数是正则表达式,用来决定替换哪些字符
  //第二个参数是匿名函数,用来确定该如何替换正则表达式搜索到的字符
  var strs = str.replace(Regex("[aeiou]")) {
      when (it.value) {
          "a" -> "1"
          "e" -> "2"
          "i" -> "3"
          "o" -> "4"
          "u" -> "5"
          else -> it.value
      }
    }
    println(strs)
}

字符串比较

在Kotlin中,用==检查两个字符串中的字符是否匹配,用===检查两个变量是否指向内存堆上同一对象,而在Java中==做引用比较,做结构比较时用equals方法。

1
2
3
4
5
6
fun main(){
  var str1 = "Jack"
  val str2 = "jack".capitalize()
  println(str1 == str2)
  println(str1 === str2)
}

遍历字符

1
2
3
4
5
fun main(){
  "The people's Republic of China".forEach {
    print("$it")
  }
}

标准库函数

apply

apply函数可看作一个配置函数,你可以传入一个接收者,然后调用一系列函数来配置它以便使用,如果提供lambda给apply函数执行,它会返回配置好的接收者。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
val file = File("/Users/qqnv/Documents/dream.txt")
//不用apply函数
file.setReadable(true)
file.setWritable(true)
file.setExecutable(false)
//apply函数
file.apply {
    setReadable(true)
    setWritable(true)
    setExecutable(false)
}

let

let函数能使某个变量作用于其lambda表达式里,让it关键字能引用它。let与apply比较,let会把接收者传给lambda,而apply什么都不传,匿名函数执行完,apply会返回当前接收者,而let会返回lambda的最后一行。

1
2
3
4
5
//不用let函数
val firstElement: Int = listOf(3, 2, 1).first()
val result = firstElement * firstElement
//使用let函数
val results = listOf(3, 2, 1).first().let { it * it }
1
2
3
4
5
6
7
8
9
//不使用let
fun formatGreeting(guestName: String?): String {
    return if (guestName != null) "Welcome $guestName." else "What's your name?"
}

//使用let
fun formatGreetings(guestName: String?): String {
    return guestName?.let { "Welcome $it." } ?: "What's your name?"
}

run

  • 光看作用域行为,run和apply差不多,但与apply不同,run函数不返回接收者,run返回的是lambda结果,可以是true或者false

    1
    2
    
    val file = File("/Users/qqnv/Documents/dream.txt")
    val result = file.run { readText().contains("dream") }
    
  • run也能用来执行函数引用

    1
    2
    3
    4
    5
    6
    7
    8
    
    val result = "The people's Republic of China".run(::isLong).run(::showMessage).run(::println)
    fun isLong(name: String) = name.length > 10
    fun showMessage(isLong: Boolean): String {
        return if (isLong)
            "Name is too long."
        else
            "Please rename."
    }
    

with

with函数是run的变体,他们的功能行为是一样的,但with的调用方式不同,调用with时需要值参作为其第一个参数传入。

1
2
val file = File("/Users/qqnv/Documents/dream.txt")
var result = with(file) { readText().contains("dream") }

also

also函数和let函数功能相似,和let一样,also也是把接收者作为值参传给lambda,但有一点不同:also返回接收者对象,而let返回lambda结果。因为这个差异,also尤其适合针对同一原始对象,利用副作用做事,既然also返回的是接收者对象,你就可以基于原始接收者对象执行额外的链式调用。

1
2
3
4
5
//also函数
val file = File("/Users/qqnv/Documents/dream.txt")
var fileContents: List<String>
file.also { println(it.name) }
    .also { fileContents = it.readLines() }

takeIf

和其他标准函数有点不一样,takelf函数需要判断lambda中提供的条件表达式,给出true或false结果,如果判断结果是true,从takelf函数返回接收者对象,如果是false,则返回null。如果需要判断某个条件是否满足,再决定是否可以赋值变量或执行某项任务,takelf就非常有用,概念上讲,takelf函数类似于if语句,但它的优势是可以直接在对象实例上调用,避免了临时变量赋值的麻烦。

1
2
3
4
5
6
7
8
9
//不用takeIf函数
val file = File("/Users/qqnv/Documents/dream.txt")
val fileContent: String? = if (file.canRead() && file.canWrite())
file.readText() else null
//takeIf函数
val file = File("/Users/qqnv/Documents/dream.txt")
val fileContent1: String? = file
    .takeIf { it.exists() && it.canRead() && it.canWrite() }
    ?.readText()

takeUnless

takelf辅助函数takeUnless,只有判断你给定的条件结果是false时,takeUnless才会返回原始接收者对象。

1
2
3
4
5
//takeUnless函数
val file = File("/Users/qqnv/Documents/dream.txt")
val fileContent: String? = file
    .takeUnless { it.isHidden }
    ?.readText()

List

list创建与元素获取

  • getOrElse是一个安全索引取值函数,它需要两个参数,第一个是索引值,第二个是能提 。 供默认值的lambda表达式,如果索引值不存在的话,可用来代替异常。
  • getOrNull是Kotlin提供的另一个安全索引取值函数,它返回null结果,而不是抛出异常。
1
2
3
4
5
6
7
fun main(){
		val list = listOf("Json", "Rose", "Merry")
    //list[4]
    println(list.getOrElse(4) { "Unknown" })
    println(list.getOrNull(4))
    println(list.getOrNull(4) ?: "Unknown")
}

可变列表

在Kotlin中,支持内容修改的列表叫可变列表,要创建可变列表,可以使用mutable ListOf函数。List还支持使用toList和toMutableList函数动态实现只读列表和可变列表的相互转换。

1
2
3
4
5
6
7
8
//可变列表
val mutableList = mutableListOf("Jack","Rose","Merry")
mutableList.add("Jane")
mutableList.remove("Merry")
println(mutableList)
//可变与只读列表相互转换
listOf("Jack","Rose","Merry").toMutableList()
mutableListOf("Jack","Rose","Merry").toList()

mutator

  • 能修改可变列表的函数有个统一的名字:mutator函数
  • 添加元素运算符与删除元素运算符(还记得C++中的运算符重载吗?)
  • 基于lambda表达式指定的条件删除元素

集合遍历

  • for in遍历
  • forEach遍历
  • forEachIndexed遍历时要获取索引
1
2
3
4
5
//集合遍历
val list = listOf("Jack","Rose","Merry")
for (s in list) println(s)
list.forEach{ println(it)}
list.forEachIndexed { index, s -> println("$index $s") }

解构

通过_符号过滤掉不想要的元素

1
2
3
//解构
val list2 = listOf("Jack","Rose","Merry")
val (origin,_,proxy) = list2

Set

Set创建与元素获取

通过setOf 创建set集合,使用elementAt函数读取集合中的元素,其他使用方法和list类似

1
2
3
4
val set = setOf("Kotlin","Java","Scala")
//不能使用该方法获取set元素
//set[2]
set.elementAt(2)

集合转换

  • 把List转换成Set,去掉重复元素
  • 快捷函数
1
2
3
4
5
//利用集合转换去除重复元素
val list = listOf("Jack", "Rose", "Merry", "Merry")
	.toSet()
	.toList()
println(listOf("Jack", "Rose", "Merry", "Merry").distinct())

数组类型

Kotlin提供各种Array,虽然是引用类型,但可以编译成Java基本数据类型

数组类型 创建函数
IntArray intArrayOf
DoubleArray doubleArrayOf
LongArray longArrayOf
ShortArray shortArrayOf
ByteArray byteArrayOf
FloatArray floatArrayOf
BooleanArray booleanArrayOf
Array arrayOf
1
2
3
4
//数组类型
val intArray = intArrayOf(1,2,3)
listOf(1,2,3).toIntArray()
arrayOf(File(""),File(""))

Map

Map的创建

to看上去像关键字,但事实上,它是个省略了点号和参数的特殊函数,to函数将它左边和右边的值转化成一对Pair。

1
2
mapOf("Jack" to 20, "Rose" to 18, "Merry" to 22)
mapOf(Pair("Jack",20), Pair("Rose",18))

读取map的值

  • []取值运算符,读取键对应的值,如果键不存在就返回null
  • getValue,读取键对应的值,如果键不存在就抛出异常
  • getOrElse,读取键对应的值,或者使用匿名函数返回默认值
  • getOrDefault,读取键对应的值,或者返回默认值
1
2
3
4
5
val map = mapOf("Jack" to 20, "Rose" to 18, "Merry" to 22)
println(map["Jack"])
println(map.getValue("Jane")) //如果找不到会抛出异常
println(map.getOrElse("Jane"){"unknown"})
println(map.getOrDefault("Jane",0))

遍历map

forEach遍历Map

1
2
3
val map1 = mapOf("Jack" to 20, "Rose" to 18, "Merry" to 22)
map1.forEach{println("${it.key}${it.value}")}
map1.forEach { (t, u) -> println("$t $u") }

可变集合

  • 通过mutableMapOf创建可变的Map
  • getOrPut键值不存在,就添加并返回结果,否则就返回已有键对应的值
1
2
3
4
5
6
7
val mutableMap = mutableMapOf("Jack" to 20, "Rose" to 18)
mutableMap += "Merry" to 24
mutableMap["Merry"] = 24

println(mutableMap.getOrPut("Merry"){18})
mutableMap.getOrPut("Jane"){18}
println(mutableMap)

Class

field

针对你定义的每一个属性,Kotlin都会产生一个field、一个getter、以及一个setter,field用来存储属性数据,你不能直接定义field,Kotlin会封装field,保护它里面的数据,只暴露给getter和setter使用。属性的getter方法决定你如何读取属性值,每个属性都有getter方法,setter方法决定你如何给属性赋值,所以只有可变属性才会有setter方法,尽管Kotlin会自动提供默认的getter和setter方法,但在需要控制如何读写属性数据时,你也可以自定义他们。

1
2
3
4
5
6
7
var name = "abc"
get() = field.capitalize()
set(value) {field = value.trim()}

var age = 10
get() = field.absoluteValue
set(value) {field = value.absoluteValue}

计算属性

计算属性是通过一个覆盖的get或set运算符来定义,这时field就不需要了。

1
2
val rolledValue
    get() = (1..6).shuffled().first()

初始化顺序

  • 主构造函数里声明的属性
  • 类级别的属性赋值
  • init初始化块里的属性赋值和函数调用
  • 次构造函数里的属性赋值和函数调用

延迟初始化

  • 使用lateinit关键字相当于做了一个约定在用它之前负责初始化
  • 只要无法确认lateinit变量是否完成初始化,可以执行islnitialized检查
1
2
3
lateinit var equipment: String
fun ready(){equipment = "Knife"}
fun battle(){if (::equipment.isInitialized) println(equipment)}

惰性初始化

延迟初始化并不是推后初始化的唯一方式,你也可以暂时不初始化某个变量,直到首次使用它,这个叫作惰性初始化。

1
2
3
4
5
6
7
8
class Player(name_: String){
  var name = name_
  val config by lazy { loadConfig() }
  private fun loadConfig(): String {
    println("loading...")
    return "xxx"
  }
}

Object

object关键字

  • 使用object关键字,你可以定义一个只能产生一个实例的类-单例
  • 使用object关键字有三种方式
    • 对象声明
    • 对象表达式
    • 伴生对象

对象声明

对象声明有利于组织代码和管理状态,尤其是管理整个应用运行生命周期内的某些一致性状态。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
object ApplicationConfig{
  init {
    println("ApplicationConfig")
  }

  fun doSomething(){
    println("doSomething")
  }
}

fun main() {
  //类名,实例名
  ApplicationConfig.doSomething()
  //打印多次会是同一个实例
	prinlin(ApplicationConfig)
  prinlin(ApplicationConfig)
}

对象表达式

有时候你不一定非要定义一个新的命名类不可,也许你需要某个现有类的一种变体实例,但只需用一次就行了,事实上,,对于这种用完就丢的类实例,连命名都可以省了。这个对象表达式是XX的子类,这个匿名类依然遵循object关键字的一个规则,即一旦实例化,该匿名类只能有唯一个实例存在。

1
2
3
4
5
6
7
8
open class Player{
  open fun load() = "loading..."
}
fun main(){
  val p = object: Player(){
    override fun load() = "anonymous loading..."
  }
}

伴生对象

如果你想将某个对象的初始化和一个类实例捆绑在一起,可以考虑使用伴生对象,使用companion修饰符,你可以在一个类定义里声明一个伴生对象,一个类里只能有一个伴生对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
open class ConfigMap {
    companion object {
        private const val PATH = "xxx"
        fun load() = File(PATH).readBytes()
    }
}

fun main() {
    //static
    ConfigMap.load()
}

嵌套类

如果一个类只对另一个类有用,那么将其嵌入到该类中并使这两个类保持在一起是合乎逻辑的,可以使用嵌套类。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
class Player2 {
    class Equipment(var name: String) {
        fun show() = println("equipment: $name")
    }
    fun battle(){}
}

fun main() {
    Player2.Equipment("knife").show()
}

数据类

  • 数据类,是专门设计用来存储数据的类
  • 数据类提供了toString的个性化实现
  • ==符号默认情况下,比较对象就是比较它们的引用值,数据类提供了equals和hash Code的个性化实现
  • 数据类支持解构语法
1
2
3
4
5
6
7
8
data class Coordinate(var x: Int, var y: Int) {
    val isInBounds = x > 0 && y > 0
}

fun main() {
  	//数据类重写了equel方法,所以返回true
    println(Coordinate(10,20) == Coordinate(10,20))
}

使用数据类的条件

正是因为上述这些特性,你才倾向于用数据类来表示存储数据的简单对象,对于那些经常需要比较、复制或打印自身内容的类,数据类尤其适合它们。然而,一个类要成为数据类,也要符合一定条件。总结下来,主要有三个方面:

  • 数据类必须有至少带一个参数的主构造函数
  • 数据类主构造函数的参数必须是val或var
  • 数据类不能使用abstract、open、sealed和inner修饰符

copy

除了重写Any类的部分函数,提供更好用的默认实现外,数据类还提供了一个函数,它可以用来方便地复制一个对象。假设你想创建一个Student实例,除了name属性,它拥有和另一个现有Student实例完全一样的属性值,如果Student是个数据类,那么复制现有Student实例就很简单了,只要调用copy函数,给想修改的属性传入值参就可以了。

解构声明

解构声明的后台实现就是声明component1、component2等若干个组件函数,让每个函数负责管理你想返回的一个属性数据,如果你定义一个数据类,它会自动为所有定义在主构造函数的属性添加对应的组件函数

1
2
3
4
5
6
7
8
9
class PlayerScore (val experience: Int,val level: Int){
    operator fun component1() = experience
    operator fun component2() = level
}

fun main() {
    val (x,y) = PlayerScore(10,20)
    println("$x $y")
}

运算符重载

如果要将内置运算符应用在自定义类身上,你必须重写运算符函数,告诉编译器该如何操作自定义类。

1
2
3
4
5
6
7
8
9
data class Coordinate2(var x: Int, var y: Int) {
    operator fun plus(other: Coordinate2) = Coordinate2(x + other.x, y + other.y)
}

fun main() {
    val c1 = Coordinate2(10, 20)
    val c2 = Coordinate2(10, 20)
    println(c1 + c2)
}

枚举类

  • 用来定义常量集合的一种特殊类
  • 枚举类可以定义一个函数

密封类

  • 对于更复杂的ADT,你可以使用Kotlin的密封类(sealed class)来实现更复杂的定义,密封类可以用来定义一个类似于枚举类的ADT,但你可以更灵活地控制某个子类型。
  • 密封类可以有若干个子类,要继承密封类,这些子类必须和它定义在同一个文件里。
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
sealed class LicenseStatus2 {
    object UnQualified : LicenseStatus2()
    object Learning : LicenseStatus2()
    class Qualified(val licenseId: String) : LicenseStatus2()
}
class Driver2(var status: LicenseStatus2){
    fun checkLicense(): String{
        return when(status) {
            is LicenseStatus2.UnQualified -> "没资格"
            is LicenseStatus2.Learning -> "在学"
            is LicenseStatus2.Qualified -> "有资格,驾驶证编号:${(this.status as LicenseStatus2.Qualified).licenseId}"
        }
    }
}

fun main() {
    val status = LicenseStatus2.Qualified("99999")
    val driver = Driver2(status).checkLicense()
    println(driver)
}

Interface

Interface定义

Kotlin规定所有的接口属性和函数实现都要使用override关键字,接口中定义的函数并不需要open关键字修饰,他们默认就是open的。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
interface Movable {
    var maxSpeed: Int
    var wheels: Int
    fun move(movable: Movable): String
}

class Car(_name: String, override var wheels: Int = 4): Movable{
    override var maxSpeed: Int
        get() = TODO("Not yet implemented")
        set(value) {}

    override fun move(movable: Movable): String {
        TODO("Not yet implemented")
    }

}

默认实现

只要你愿意,你可以在接口里提供默认属性的getter方法和函数实现。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
interface Movable {
    val maxSpeed: Int
        get() = (0..4).shuffled().last()
    var wheels: Int
    fun move(movable: Movable): String
}

class Car(_name: String, override var wheels: Int = 4): Movable{
    override var maxSpeed: Int
        get() = super.maxSpeed
        set(value) {}

    override fun move(movable: Movable): String {
        TODO("Not yet implemented")
    }

}

抽象类

定义

要定义一个抽象类,你需要在定义之前加上abstract关键字,除了具体的函数实现,抽象类也可以包含抽象函数一只有定义,没有函数实现。

泛型类

泛型类的构造函数可以接受任何类型。
MagicBox类指定的泛型参数由放在一对<>里的字母T表示,T是个代表item类型的占位符。MagicBox类接受任何类型的item作为主构造函数值(item:T),并将item值赋给同样是T类型的subject私有属性。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
class MagicBox<T>(item: T) {
    private var subject: T = item
}

class Boy(val name: String, val age: Int)

class Dog(val weight: Int)

fun main() {
    val box1: MagicBox<Boy> = MagicBox(Boy("Jack",18))
    val box2: MagicBox<Dog> = MagicBox(Dog(20))
}

函数

泛型函数

  • 泛型参数也可以用于函数。
  • 定义一个函数用于获取元素,当且仅当MagicBox可用时,才能获取元素。

多泛型参数

泛型函数或泛型类也可以有多个泛型参数

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
package kot.inter

/**
 * 泛型类及泛型约束
 */
class MagicBox<T : Human>(item: T) {
    var available = false
    private var subject: T = item

    fun fetch(): T? {
        return subject.takeIf { available }
    }

    fun <R> fetch(subjectModFunction: (T) -> R): R? {
        return subjectModFunction(subject).takeIf { available }
    }
  
  	//运算符重载
    operator fun get(index: Int): T? = subject[index]?.takeIf { available }
}

open class Human(val age: Int)

class Boy(val name: String, age: Int) : Human(age)

class Man(val name: String, val age: Int)

class Dog(val weight: Int)

fun main() {
    val box1: MagicBox<Boy> = MagicBox(Boy("Jack", 18))
//    val box2: MagicBox<Dog> = MagicBox(Dog(20))
    box1.available = true

    box1.fetch()?.run {
        println("you find $name")
    }

    val man = box1.fetch() {
        Man(it.name, it.age.plus(10))
    }
  
  	//运算符重载后取值
  	box1[1]
}

out

out(协变),如果泛型类只将泛型类型作为函数的返回(输出),那么使用out,可以称之为生产类/接口,因为它主要是用来生产(produce)指定的泛型对象。

1
2
3
interface Production<out T> {
  fun product(): T
}

in

in(逆变),如果泛型类只将泛型类型作为函数的入参(输入),那么使用in,可以称之为消费者类/接口,因为它主要是用来消费(consume)指定的泛型对象。

1
2
3
interface Consumer<in T> {
  fun consumer(item: T)
}

invariant

invariant(不变),如果泛型类既将泛型类型作为函数参数,又将泛型类型作为函数的输出,那么既不用out也不用in。

1
2
3
4
interface ProductionConsumer<T> {
  fun product(): T
  fun consumer(item: T)
}

父类泛型对象可以赋值给子类对象,用in

子类泛型对象可以赋值给父类对象,用out

reified

有时候,你可能想知道某个泛型参数具体是什么类型,reified关键字能帮你检查泛型参数类型。Kotlin不允许对泛型参数T做类型检查,因为泛型参数类型会被类型擦除,也就是说,T的类型信息在运行时是不可知的,Java也有这样的规则。

定义扩展函数

扩展可以在不直接修改类定义的情况下增加类功能,扩展可以用于自定义类,也可以用于比如List、String,以及Kotlin标准库里的其他类。和继承相似,扩展也能共享类行为,在你无法接触某个类定义,或者某个类没有使用open修饰符,导致你无法继承它时,扩展就是增加类功能的最好选择。

1
2
3
4
5
6
7
fun String.addExt(amount: Int = 1) = this + "!".repeat(amount)
fun Any.easyPrint() = println(this)

fun main() {
    println("abc".addExt(2))
  	"abc".easyPrint()
}

泛型扩展函数

新的泛型扩展函数不仅可以支持任何类型的接收者,还保留了接收者的类型信息使用泛型类型后,扩展函数能够支持更多类型的接收者,适用范围更广了。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fun String.addExt(amount: Int = 1) = this + "!".repeat(amount)

fun <T> T.easyPrint(): T {
  println(this)
  return this
}

fun main() {
    "abc".easyPrint().addExt(2).easyPrint()
}

扩展属性

除了给类添加功能扩展函数外,你还可以给类定义扩展属性,给String类添加一个扩展,这个扩展属性可以统计字符串里有多少个元音字母。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
val String.numVowels
    get() = count { "aeiou".contains(it) }

fun <T> T.easyPrint(): T {
  println(this)
  return this
}

fun main() {
    "The people's Republic of China.".numVowels.easyPrint()
}

可空类扩展

你也可以定义扩展函数用于可空类型,在可空类型上定义扩展函数,你就可以直接在扩展函数体内解决可能出现的空值问题。

1
2
3
4
5
6
fun String?.printWithDefault(default: String) = print(this ?: default)

fun main() {
    val nullableString: String? = null
    nullableString.printWithDefault("abc")
}

infix关键字

infix关键字适用于有单个参数的扩展和类函数,可以让你以更简洁的语法调用函数,如果一个函数定义使用了infix关键字,那么调用它时,接收者和函数之间的点操作以及参数的一对括号都可以不要。

1
2
3
4
5
6
infix fun String?.printWithDefault(default: String) = print(this ?: default)

fun main() {
    val nullableString: String? = null
    nullableString printWithDefault "abc"
}

定义扩展文件

扩展函数需要在多个文件里使用,可以将它定义成单独的文件,然后import

1
2
3
package kot.func

fun <T> Iterable<T>.randomTake(): T = this.shuffled().first()
1
2
3
4
5
6
7
import kot.func.randomTake

fun main(){
  val list = listof("Jason","Jack","Tom")
  val set = setof("love","hate","like")
  list.randomTake()
}

重命名扩展

有时候,你想使用一个扩展或一个类,但它的名字不和你的意。可使用“as”

1
2
3
4
5
6
7
import kot.func.randomTake as randomer

fun main(){
  val list = listof("Jason","Jack","Tom")
  val set = setof("love","hate","like")
  list.randomer()
}

Kotlin标准库中的扩展

Kotlin标准库提供的很多功能都是通过扩展函数和扩展属性来实现的,包含类扩展的标准库文件通常都是以类名加s后缀来命名的,例如Sequences.kt,Ranges.kt,Maps.kt。

DSL

带接收者的函数字面量

apply函数是如何做到支持接收者对象的隐式调用的

1
2
3
4
public inline fun <T> T.apply(block: T.() -> Unit): T {
    block()
    return this
}

DSL

使用这样的编程范式,就可以写出业界知名的“领域特定语言”(DSL) ,一种API编程范式,暴露接收者的函数和特性,以便于使用你定义的lambda表达式来读取和配置它们。

变换

函数式编程

我们一直在学习面向对象编程范式,另一个较知名的编程范式是诞生于20世纪50年,基于抽象数学的入演算发展而来的函数式编程,尽管函数式编程语言更常用在学术而非商业软件领域,但它的一些原则适用于任何编程语言。函数式编程范式主要依赖于高阶函数(以函数为参数或返回函数)返回的数据,这些高阶函数专用于处理各种集合,可方便地联合多个同类函数构建链式操作以创建复杂的计算行为。Kotlin支持多种编程范式,所以你可以混用面向对象编程和函数式编程范式来解决手头的问题。

乍看之下,实现同样的任务,Java版本和函数式版本的代码量差不多,但仔细分析一下,就能看出函数式版本的诸多优势。

  • 累加变量(employeeShirtSizes)都是隐式定义的。
  • 函数运算结果会自动赋值给累加变量,降低了代码出错的机会。
  • 执行新任务的函数很容易添加到函数调用链上,因为他们都兼容Iterable类型。

函数类别

一个函数式应用通常由三大类函数构成:变换transform、过滤filter、合并combine。每类函数都针对集合数据类型设计,目标是产生一个最终结果。函数式编程用到的函数生来都是可组合的,也就是说,你可以组合多个简单函数来构建复杂的计算行为。

变换

变换是函数式编程的第一大类函数,变换函数会遍历集合内容,用一个以值参形式传入的变换器函数,变换每一个元素,然后返回包含已修改元素的集合给链上的其他函数。
最常用的两个变换函数是map和flatMap。

map

  • map变换函数会遍历接收者集合,让变换器函数作用于集合里的各个元素,返回结果是包含已修改元素的集合,会作为链上下一个函数的输入。
  • 可以看到,原始集合没有被修改,map变换函数和你定义的变换器函数做完事情后,返回的是一个新集合,这样,变量就不用变来变去了。
  • 事实上,函数式编程范式支持的设计理念就是不可变数据的副本在链上的函数间传递。
  • map返回的集合中的元素个数和输入集合必须一样,不过返回的新集合里的元素是不同类型的。

flatmap

flatMap函数操作一个集合的集合,将其中多个集合中的元素合并后返回一个包含所有元素的单一集合。

过滤

  • 过滤是函数式编程的第二大类函数,过滤函数接受一个predicate函数,用它按给定条件检查接收者集合里的元素并给出true或false的判定。如果predicate函数返回true,受检元素就会添加到过滤函数返回的新集合里。如果predicate函数返回false,那么受检元素就被移出新集合。
  • filter过滤函数接受一个predicate函数,在flatMap遍历它的输入集合中的所有元素时,filter函数会让predicate函数按过滤条件,将符合条件的元素都放入它返回的新集合里。最后,flatMap会把变换器函数返回的子集合合并在一个新集合里。
1
2
3
4
5
6
7
8
//变换过滤集合
val items = listOf(
  listOf("red apple", "green apple", "blue apple"),
  listOf("red fish", "blue fish"),
  listOf("yellow banana", "teal banana")
)

items.flatMap { it.filter { it.contains("red") } }.easyPrint()

filter

找素数,除了1和它本身,不能被任何数整除的数。仅使用了几个简单函数,我们就解决了找素数这个比较复杂的问题,这就是函数式编程的独特魅力:每个函数做一点,组合起来就能干大事。

1
2
3
4
5
6
7
//组合使用找素数,除了1和它本身,不能被任何数整除的数
    val numbers = listOf(7, 4, 8, 4, 3, 22, 18, 11)
    numbers.filter { numbers ->
        (2 until numbers)
            .map { numbers % it }
            .none { it == 0 } 
    }.easyPrint()

合并

合并是函数式编程的第三大类函数,合并函数能将不同的集合合并成一个新集合,这个接收者是包含集合的集合的flatMap函数不同。

zip

zip合并函数来合并两个集合,返回一个包含键值对的新集合。

1
2
3
4
5
//zip合并集合
val employees = listOf("Jack", "Jason", "Tommy")
val age = listOf(18, 20, 16)
val employeesAge = employees.zip(age).toMap()
employeesAge["Jack"].easyPrint()

fold

另一个可以用来合并值的合并类函数是fold,这个合并函数接受一个初始累加器值,随后会根据匿名函数的结果更新。

1
2
3
4
//fold合并函数,将每个元素*3然后累加
listOf(1, 2, 3, 4).fold(0) { accumulator, number ->
    accumulator + number * 3
}.easyPrint()

序列

  • List、 Set、Map集合类型,这几个集合类型统称为及早集合(eager collection) 这些集合的任何一个实例在创建后,它要包含的元素都会被加入并允许你访问。对应及早集合,Kotlin还有另外一类集合:惰性集合(lazy collection)类似于类的惰性初始化,惰性集合类型的性能表现优异,尤其是用于包含大量元素的集合时,因为集合元素是按需产生的。
  • Kotlin有个内置惰性集合类型叫序列(Sequence) ,序列不会索引排序它的内容,也不记录元素数目,事实上,在使用一个序列时,序列里的值可能有无限多,因为某个数据源能产生无限多个元素。
  • 针对某个序列,你可能会定义一个只要序列有新值产生就被调用一下的函数,这样的函数叫迭代器函数,要定义一个序列和它的迭代器,你可以使用Kotlin的序列构造函数generateSequence,generateSequence函数接受一个初始种子值作为序列的起步值,在用generateSequence定义的序列上调用一个函数时,generateSequence函数会调用你指定的迭代器函数,决定下一个要产生的值。

注解

  • JvmName
    @file:JvmName(“”)->给kotlin文件加该注解,在Java中访问里面的属性
  • JvmField
    @JvmField->给kotlin中类的参数加该注解,在Java中无需get方法访问
    以静态方式提供伴生对象里定义的值
  • JvmOverloads
    @JvmField->设计一个可能会暴露给Java用户的API,使用该注解,强制重载
  • JvmStatic
    @JvmStatic->类似于Jv mField,允许直接调用伴生对象里的函数
分享

qqnv
作者
qqnv
Android Developer