这个笔记根据B站视频2021最新最全Kotlin教程Android程序员定制版,Java转Kotlin学它就够了!更新完毕总结

Kotlin

基础语法

定义变量

var num:Int = 5

image-20230323215731652

  • 可修改变量使用var
  • 只读变量,使用val
  • 预编译常量使用const
const val Max = 200
fun main() {
    var a: String = "hello"
    println(a)
    println(Max)

range

range(范围)

1..3 的含义就是 大于等于1小于等于3,数学表示: [1 , 3]

    val age = 5
    if (age in 1..3) {
        println("婴幼儿")
    } else if (age in 3..12) {
        println("少儿")
    }

when

//    =========================when 主要替代 switch case
    val school = "小学"
    val level = when (school) {
        "学前班" -> "幼儿"
        "小学" -> "少儿"
        else -> {
            println("不知道")
        }
    }

字符串模板

在字符串里面$+变量名可以直接调用变量

//    ========================== 字符串模板
    val orgin = "bob"
    println("$orgin is  bob")
    val flag = false
    println("Answer is ${if (flag) "我可以" else "比不起"} ")

函数

//=========================函数
// 修饰符  关键字 函数名(参数:参数类型,参数2:参数2类型):返回值类型 {}
private fun doSomething(a: Int, b: Int = 2): String {
    println("函数默认值:$b")
    return a.toString()
}
//    ============================函数
    println(doSomething(1))
//    =============================具名的函数参数
    println(doSomething(a = 1, b = 1))

//没有返回值的函数叫做 Unit函数
fun Unit(){
    println("Unit")
}
//  ============================= 无返回类型的类型是kotlin.Unit
    println(Unit())

Nothing类型

TODO函数的任务就是抛出异常,就是永远别指望它运行成功,返回Nothing类型。

//    todo 是用来抛出异常的
    TODO("fac")
}

匿名函数

定义时不取名字的函数,我们称之为匿名函数,匿名函数通常整体传递给其他函数,或者从其他函数返回。
匿名函数对Kotlin来说很重要,有了它,我们能够根据需要制定特殊规则,轻松定制标准库里的内置函数。

fun main() {
    val total = "Mississippi".count()

    val totalS = "Mississippi".count{it == 's'}

    println(total)
    println(totalS)

    //变量的类型是一个匿名函数
    val blessingFunction5:()->String = {
        val holiday = "New Year."
        "Happy $holiday"
    }
// 在后边定义参数名字
    val blessingFunction4:(String) -> String = { name ->
        val holiday = "New Year."
        "$name, Happy $holiday"
    }
// 不写参数名字的话就是用 it
    val blessingFunction3:(String) -> String = {
        val holiday = "New Year."
        "$it, Happy $holiday"
    }
// 类型推断返回值类型
    val blessingFunction2 = {
        val holiday = "New Year."
        "Happy $holiday"
    }
// 传入参数的匿名函数
    val blessingFunction1:(String,Int) -> String = {name, year ->
        val holiday = "New Year."
        "$name, Happy $holiday $year"
    }

// 类型推断+ lambda
    val blessingFunction = {name:String, year:Int ->
        val holiday = "New Year."
        "$name, Happy $holiday $year"
    }

    println(blessingFunction("Jack",2027))

}

匿名函数的简略写法

如果一个函数的lambda参数排在最后,或者是唯一的参数,那么括住lambda值参的一对圆括号就可以省略。

fun main() {
    showOnBoard("卫生纸"){goodsName:String, hour:Int ->
        val currentYear = 2027
        "${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
    }
}

//具名函数
private fun showOnBoard(goodsName:String, getDiscountWords: (String,Int) -> String){
    val hour = (1..24).shuffled().last()
    println(getDiscountWords(goodsName,hour))
}

匿名函数当参数

fun main() {
    val getDiscountWords = {goodsName:String, hour:Int ->
        val currentYear = 2027
        "${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
    }

    showOnBoard("卫生纸",getDiscountWords)
}

//具名函数
private inline fun showOnBoard(goodsName:String, getDiscountWords: (String,Int) -> String){
    val hour = (1..24).shuffled().last()
    println(getDiscountWords(goodsName,hour))
}

const val MAX = 500

具名函数当参数

fun main() {
    showOnBoard("牙膏",::getDiscountWords)
}

private fun getDiscountWords(goodsName: String,hour: Int):String{
    val currentYear = 2027
    return "${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
}

private fun showOnBoard(goodsName:String, getDiscountWords: (String,Int) -> String){
    val hour = (1..24).shuffled().last()
    println(getDiscountWords(goodsName,hour))
}

函数的内联

 private inline  fun fetch(index: Int): String {
        return subject[index].takeIf { available }
}

lambda可以让你更灵活地编写应用,但是,灵活也是要付出代价的。
在VM上,你定义的lambda会以对象实例的形式存在,VM会为所有同ambda打交道的变量分配内存,这就产生了内存开销。更糟的是,lambda的内存开销会带来严重的性能问题。幸运的是,kotlin有一种优化机制叫内联,有了内联,JVM就不需要使用lambda对象实例了,因而避免了变量内存分配。哪里需要使用lambda,编译器就会将函数体复制粘贴到哪里。
使用lambda的递归函数无法内联,因为会导致复制粘贴无限循环,编译会发出警告。

闭包

  • 在Kotlin中,匿名函数能修改并引用定义在自己的作用域之外的变量,匿名函数引用着定义自身的函数里的变量,Kotlin中的lambda就是闭包。
  • 能接收函数或者返回函数的函数又叫做高级函数,高级函数广泛应用于函数式编程当中。
fun main() {
    val getDiscountWords = configDiscountWords()
    println(getDiscountWords("沐浴露"))
}

fun configDiscountWords(): (String) -> String{
    val currentYear = 2027
    val hour = (1..24).shuffled().last()
    return {goodsName: String ->
        "${currentYear}年,双11${goodsName}促销倒计时:$hour 小时"
    }
}

标准库函数

apply

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

image-20230324083732009

可以看到,调用一个个函数类配置接收者时,变量名就省掉了,这是因为,在lambda表达式里,applyi能让每个配置函数都作用于接收者,这种行为有时又叫做相关作用域,因为lambda表达式里的所有函数调用都是针对接收者的,或者说,它们是针对接收者的隐式调用。

let

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

    val result = listOf(1, 2, 3, 4).first().let {
        it * it
    }
    println(result)

run

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

image-20230324090626856

执行函数的引用

image-20230324090709455

fun main() {
  "abdefghijklmnop".run(::len).run(::println)
}
fun len(str: String): Boolean {
    return str.length >= 10
}

结果打印 true

with

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

    var res = with("abdefghijklmnop") {
        length > 10
    }
    println(res)

also

also函数和let函数功能相似,和let一样,also也是把接收者作为值参传给lambda,但有一点不同:

  • also返回接收者对象
  • let返回lambda结果。

因为这个差异,also尤其适合针对同一原始对象,利用副作用做事,既然also返回的是接收者对象,你就可以基于原始接收者对象执行额外的链式调用。

image-20230324093254201

image-20230324093254201

image-20230324093309294

takeif

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

List

//    不可变列表
val list = listOf(1, 2, 3, 4, 5)
    println(list[2])
    println(list.getOrElse(38
    ){"Unknown"})
//    可变列表
    val mutableList = mutableListOf(1, 2, 3, 4)
    mutableList.addAll(list)
    mutableList.remove(1)  // remove element ,not is index
//相互转换
 val list1 = mutableList.toList()
 val toMutableList = list.toMutableList()

mutator函数

//    mutator function
    val mutableList = mutableListOf(1, 2, 3, 4)
    mutableList += 10
    println(mutableList)
    mutableList -= 5
    println(mutableList)
    val mutableListOf = mutableListOf("a", "b", "c")
    val removeIf = mutableListOf.removeIf { it.contains("a") }
    println(removeIf)

遍历

fun main() {
    val list = listOf("Jason", "Jack", "Jacky")
    for (s in list) {
        println(s)
    }

    list.forEach {
        println(it)
    }

    list.forEachIndexed { index, item ->
        println("$index, $item")
    }

    val (origin,_,proxy)  = list
}

Set

基本和list相同

//    set
    val set = setOf("a", "b", "c", "d")
    val mutableSet = mutableSetOf("a", "b", "c", "d")
    mutableSet += "e"
    val toSet = mutableList.toSet().toList()
    mutableList.distinct()

数组类型

image-20230324103706751

Map

image-20230324103824756

image-20230324104550686

//    map
    val map = mapOf("a" to "b", "c" to "d")
    println(map["a"])
    println(map.getValue("a"))
    println(map.getOrElse("a") { "undefined" })
    println(map.getOrDefault("a") { "ab" })
    for (entry in map) {
        println(entry.key + " " + entry.value)
    }
    map.forEach {
        println(it.key + "_" + it.value)
    }
    map.forEach { (k, v) ->
        println("$k,$v")
    }
    val mutableMap = map.toMutableMap()
    mutableMap += "e" to "f"
    mutableMap["i"] = "j"
    mutableMap.put("g","h")
    mutableMap.getOrPut("k"){"l"}

field

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

class Player {
    var name = "zioplyyc"
        get() = field.uppercase()
        set(value) {
            field = value.trim()
        }
}

fun main() {
    var p = Player()
    p.name = "ziop"
    println(p.name)
    p.name = " ziop  "
    println(p.name)
}

计算属性

class Player {
    var name = "zioplyyc"
//        get() = field.uppercase()
        get() = (1..6).shuffled().first().toString() // 计算属性
        set(value) {
            field = value.trim()
        }
}

防范竞态条件

如果一个类属性既可空又可变, 那么引用它之前你必须保证它非空, 一个办法是用also标准函数

image-20230324110513493

主构造函数

我们在Player类的定义头中定义一个主构造函数,使用临时变量为Playert的各个属性提供初始值,在Kotlin中,为便于识别,临时变量(包括仅引用一次的参数),通常都会以下划线开头的名字命名。

两种写法:

class ClassDefinition(
    
    _name: String,
    _age: Int,
    var isNormal: Boolean

) {
    var name = _name
        get() = field.uppercase()
    var age = _age
}

fun main() {
    val classDefinition = ClassDefinition("ziop", 18, false)
    val age = classDefinition.name
    println(age)
    println(classDefinition.age)
    println(classDefinition.age)
    println(classDefinition.isNormal)
}

次构造函数

constructor(name: String) : this(name, _age = 10, isNormal = false){}

class ClassDefinition(
    _name: String,
    _age: Int,
    var isNormal: Boolean

) {
    var name = _name
        get() = field.uppercase()
    var age = _age

    constructor(name: String) : this(name, _age = 10, isNormal = false){
        //逻辑代码
    }
}

fun main() {
    val classDefinition = ClassDefinition("ziop", 18, false)
    val age = classDefinition.name
    println(age)
    println(classDefinition.age)
    println(classDefinition.age)
    println(classDefinition.isNormal)
    val lyyc = ClassDefinition("lyyc")
    println(lyyc.name)
}

初始化块

init{} 类初始化的时候执行,不是类加载时候执行

    init {
        require(age in 0..100){"年龄不合法"}
    }

执行顺序

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

image-20230324114323891

延迟初始化&惰性初始化

class Player02 {
//    手动延迟初始化
    lateinit var equipment:String
    fun ready() {
        equipment="sharp knife"
    }
    fun battle(){
        if (::equipment.isInitialized) println(equipment)
    }

//    惰性初始化
    val config by lazy {loadConfig()}
    private fun loadConfig():String{
        println("loading......")
        return "xxx"
    }
}

fun main() {
    var p1 = Player02()
    p1.ready()
    p1.battle()
    println(p1.config)
}

初始化陷阱

  • 顺序非常重要,kotlin 从上到下加载

image-20230324132517163

  • 编译时没问题,运行时问题

image-20230324132710838

  • 错误代码

image-20230324132900453

继承和覆盖&&智能类型转&&Any超类

  • 只有父类含有 open才能被集成,只有方法包含open才能被重写

  • AS 可以进行智能类型转换,只要进行一次as 以后就不用了

    • 因为声明的时候声明的是父类,但是父类并没有special方法,如果直接调用会出问题,所以使用as转化成子类,以后就不用转换了
  • kotlin的所有类的父类是 Any超类

open class Product(val name: String) {
    fun description() = "Product: $name"

    open fun load() = "Nothing.."
}

class LuxuryProduct : Product("Luxury") {

    override fun load() = "LuxuryProduct loading..."

    fun special() = "LuxuryProduct special function"
}
fun main() {
    val p: Product = LuxuryProduct()
    println(p.load())
// 类型检测
    println(p is Product)  // true
    println(p is LuxuryProduct) // true
    println(p is File) //false
    // 智能类型转换
    println((p as LuxuryProduct).special())
    println(p.special())

    println(p is Any)
    println(p.toString())
}    

对象

Object关键字

使用object关键字,你可以定义一个只能产生一个实例的类单例
使用object关键字有三种方式

  • 对象声明
  • 对象表达式
  • 伴生对象

对象声明

使用的时候直接使用类名+方法,这个类名即时类名,有是单例对象

object ApplicationConfig{
    init {
        println("ApplicationConfig loading...")
    }
    
    fun doSomething(){
        println("doSomething")
    }
}

fun main() {
    //类名,实例名
    ApplicationConfig.doSomething()
    println(ApplicationConfig)
    println(ApplicationConfig)
}

对象表达式

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

open class Player{
    open fun load() = "loading nothing.."
}

fun main() {
    val p = object : Player(){
        override fun load() = "anonymous nothing.."
    }

    println(p.load())
}

伴生对象

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

  • 只有初始化 ConfigMap类,或者调用load函数的时候,伴生对象的内容才会载入,并且伴生对象只有一次
import java.io.File

open class ConfigMap{

    companion object{
        private const val PATH= "xxxx"

        fun load() = File(PATH).readBytes()
    }

}

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

嵌套类

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

class Player2 {

    class Equipment(var name: String) {
        fun show() = println("equipment:$name")
    }
    fun battle(){

    }

}

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

数据类

  • 数据类,是专门设计用来存储数据的类
  • 数据类提供了toString的个性化实现
  • ==符号默认情况下,比较对象就是比较它们的引用值,数据类提供了equals和hashCode的个性化实现
  • 自带实现结构语法
data class Coordinate(var x: Int, var y: Int) {
    val isInBounds = x > 0 && y > 0
}

fun main() {
    println(Coordinate(10, 20))
    // == 比较的是内容,equals,Any 默认实现===,比较引用
    // === 比较的是引用
    println(Coordinate(10, 20) == Coordinate(10, 20))
    println(Coordinate(10, 20) === Coordinate(10, 20))

    val (x, y) = Coordinate(10, 20)
    println("$x, $y")
}
//Coordinate(x=10, y=20)
//true
//false
//10, 20

copy

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

数据类提供了一个对象拷贝的方法,但是需要注意的是拷贝的时候并不会去执行次构造方法

data class Student(var name: String, val age: Int) {
    private val hobby = "music"
    val subject: String
    var score = 0

    init {
        println("initializing student")
        subject = "math"
    }

    constructor(_name: String) : this(_name, 10){
        score = 10
    }

    override fun toString(): String {
        return "Student(name='$name', age=$age, hobby='$hobby', subject='$subject', score=$score)"
    }


}

fun main() {
    val  s = Student("Jack")
    val copy = s.copy("Rose")
    println(s)
    println(copy)
}

结构语法

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

注意:

  • 这里的component是从1 开始的,写0会报错,写3也会报错,必须顺着写
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")
}

运算符重载

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

image-20230324152823745

操作符函数名作用
+plus把一个对象添加到另一个对象里
+=plusAssign把一个对象添加到另一个对象里,然后将结果赋值给第一个对象
==equals如果两个对象相等,则返回true,否则返回false
>compateTo如果左边的对象大于右边的对象,则返回true,否则返回false
[]get返回集合中指定位置的元素
..rangeTo创建一个range对象
incontains如果对象包含在集合里,则返回true
data class Coordinate2(var x: Int, var y: Int) {
    val isInBounds = x > 0 && y > 0

    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)
}

使用数据类的条件

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

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

枚举类

枚举类也可以添加函数

enum class Direction{
    EAST,
    WEST,
    SOUTH,
    NORTH
}
enum class Direction2(private val coordinate: Coordinate){
    EAST(Coordinate(1,0)),
    WEST(Coordinate(-1,0)),
    SOUTH(Coordinate(-1,0)),
    NORTH(Coordinate(1,0));

    fun updateCoordinate(playerCoordinate: Coordinate) =
        Coordinate(playerCoordinate.x + coordinate.x,
            playerCoordinate.y + coordinate.y)

}
fun main() {
    println(Direction.EAST)
    println(Direction.EAST is Direction)
    println(Direction2.EAST.updateCoordinate(Coordinate(10,20)))
}

代数数据类型

可以用来表示一组子类型的闭集,枚举类就是一种简单的ADT。

enum class LicenseStatus {
    UNQUALIFIED,
    LEARNING,
    QUALIFIED;
}
class Driver(var status: LicenseStatus) {
    fun checkLicense(): String {
        return when(status){
            LicenseStatus.UNQUALIFIED -> "没资格"
            LicenseStatus.LEARNING -> "在学"
            LicenseStatus.QUALIFIED -> "有资格"
        }
    }
}

fun main() {
    println(Driver(LicenseStatus.QUALIFIED).checkLicense())
}

密封类

  • 对于更复杂的ADT,你可以使用Kotlin的密封类(sealed class)来实现更复杂的定义,密封类可以用来定义一个类似于枚举类的ADT,但你可以更灵活地控制某个子类型。
  • 密封类可以有若干个子类,要继承密封类,这些子类必须和它定义在同一个文件里
//密封
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("238239329")
    val driver = Driver2(status)
    println(driver.checkLicense())
}

接口

定义接口

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

  • 在实现的时候 可以将val 改成 var
interface Movable {
    val maxSpeed: Int
        get() = (1..500).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 implemented") //To change body of created functions use File | Settings | File Templates.
    }

}

抽象类

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

abstract class Gun(val range: Int) {

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

    abstract fun pullTrigger(): String
}

//多重继承

class AK47(val price: Int) : Gun(range = 500){
    override fun pullTrigger(): String {
        TODO("not implemented")
    }
}

泛型

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

定义泛型类

泛型参数通常用字母T(代表英文type)表示,当然,想用其他字母,甚至是英文单词都是可以的。不过,其他支持泛型的语言都在用这个约定俗成的T,所以建议你继续用它,这样写出的代码别人更容易理解。

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",20))
    val box2:MagicBox<Dog> = MagicBox(Dog(20))
}

泛型函数

  • 泛型参数也可以用于函数。

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

class MagicBox<T>(item: T) {
    var available = false
    private var subject: T = item

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

}

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

class Dog(val weight: Int)

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

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

多泛型参数

class MagicBox<T>(item: T) {
    var available = false
    private var subject: T = item

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

    //业务,把元素进行修改
    //魔盒里面放的是男孩,取出来的时候,我给他改成了一个男人
    //return -> R
    fun <R> fetch(subjectModFunction: (T) -> R): R? {
        return subjectModFunction(subject).takeIf { available }
    }

}

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

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

class Dog(val weight: Int)

fun main() {
    val box1: MagicBox<Boy> = MagicBox(Boy("Jack", 15))
    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(15))
    }
}

泛型约束

class MagicBox<T : Human>(item: T) {
    var available = false
    private var subject: T = item

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

    //业务,把元素进行修改
    //魔盒里面放的是男孩,取出来的时候,我给他改成了一个男人
    //return -> R
    fun <R> fetch(subjectModFunction: (T) -> R): R? {
        return subjectModFunction(subject).takeIf { available }
    }

}

open class Human(val age: Int)

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

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

class Dog(val weight: Int)

fun main() {
    val box1: MagicBox<Boy> = MagicBox(Boy("Jack", 15))
    //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(15))
    }
}

vararg关键字与get函数

  • 如果想要给方法添加多个同类型参数的话使用 vararg

    • class MagicBox<T : Human>(vararg item: T) {}
      
  • 存入数组的时候还需要使用 out关键字(out后边有解释)

    • private var subject: Array<out T> = item
      
class MagicBox<T : Human>(vararg item: T) {
    var available = false
    private var subject: Array<out T> = item

    fun fetch(index: Int): T? {
        return subject[index].takeIf { available }
    }

    //业务,把元素进行修改
    //魔盒里面放的是男孩,取出来的时候,我给他改成了一个男人
    //return -> R
    fun <R> fetch(index: Int, subjectModFunction: (T) -> R): R? {
        return subjectModFunction(subject[index]).takeIf { available }
    }
}

open class Human(val age: Int)

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

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

class Dog(val weight: Int)

fun main() {
    
    val box1: MagicBox<Boy> = MagicBox(
        Boy("Jack", 15),
        Boy("Jacky", 16),
        Boy("John", 26)
    )
    box1.available = true

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

    val man = box1.fetch(2) {
        Man(it.name, it.age.plus(15))
    }
}

in与out

  • out(协变)

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

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

    • 如果泛型类既将泛型类型作为函数参数,又将泛型类型作为函数的输出,那么既不用Out也不用in。

image-20230324194650441

//out
interface Production<out T> {
    fun product(): T
}

//in
interface Consumer<in T> {
    fun consume(item: T)
}

//不变
interface ProductionConsumer<T> {
    fun product(): T
    fun consume(item: T)
}

open class Food
open class FastFood : Food()
class Burger : FastFood()

//生产者
//食品商店
class FoodStore : Production<Food>{
    override fun product(): Food {
        println("Produce food.")
        return Food()
    }
}
//快餐商店
class FastFoodStore : Production<FastFood>{
    override fun product(): FastFood {
        println("Produce FastFood.")
        return FastFood()
    }
}

//汉堡商店
class BurgerStore : Production<Burger>{
    override fun product(): Burger {
        println("Produce Burger.")
        return Burger()
    }
}

//消费者
class Everybody : Consumer<Food>{
    override fun consume(item: Food) {
        println("Eat food.")
    }
}

class ModernPeople : Consumer<FastFood>{
    override fun consume(item: FastFood) {
        println("Eat fastFood.")
    }
}

class American : Consumer<Burger>{
    override fun consume(item: Burger) {
        println("Eat burger.")
    }
}


fun main() {
    //赋值
    //子类泛型对象可以赋值给父类泛型对象,用 out。
    val production1: Production<Food> = FoodStore()
    val production2: Production<Food> = FastFoodStore()
    val production3: Production<Food> = BurgerStore()

    //父类泛型对象可以赋值给子类泛型对象,用 in。
    val consumer1: Consumer<Burger> = Everybody()
    val consumer2: Consumer<Burger> = ModernPeople()
    val consumer3: Consumer<Burger> = American()

}

reified关键字

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

class MagicBox<T : Human>() {

/*    //随机产生一个对象,如果不是指定类型的对象,就通过backup函数生成一个指定类型的对象
    fun <T> randomOrBackup(backup: () -> T): T {
        val items = listOf(
            Boy("Jack", 20),
            Man("John", 35)
        )
        val random = items.shuffled().first()
        return if (random is T) {
            random
        } else {
            backup()
        }
    }*/


    inline fun <reified T> randomOrBackup(backup: () -> T): T {
        val items = listOf(
            Boy("Jack", 20),
            Man("John", 35)
        )
        val random = items.shuffled().first()
        println(random)
        return if (random is T) {
            random
        } else {
            backup()
        }
    }

}

open class Human(val age: Int)
class Boy(val name: String, age: Int) : Human(age) {
    override fun toString(): String {
        return "Boy(name='$name',age='$age')"
    }
}

class Man(val name: String, age: Int) : Human(age) {
    override fun toString(): String {
        return "Man(name='$name',age='$age')"
    }
}


fun main() {
    val box1: MagicBox<Boy> = MagicBox()
    //又backup函数,推断出来T的类型
    val subject = box1.randomOrBackup {
        Boy("Jimmy", 38)
    }
    println(subject)
}

扩展

定义扩展函数

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

  • 不能重复定义,会直接报错
  • image-20230324202546249
  • image-20230324202704880
//给字符串追加若干个感叹号
fun String.addExt(amount: Int = 1) = this + "!".repeat(amount)


fun Any.easyPrint() = println(this)

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

泛型扩展函数

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

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()

    val i = "abc".let {
        50
    }
}

扩展属性

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

val String.numVowels
    get() = count { "aeiou".contains(it) }


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

可空类扩展

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

infix

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

infix fun String?.printWithDefault(default: String) = print(this ?: default)

fun main() {
    val nullableString: String? = null
    nullableString printWithDefault "abc"
    //"jack".to(18)
    //mapOf("jack" to 18)
}

定义扩展文件

扩展函数需要在多个文件里面使用,可以将它定义在单独的文件,然后import。并且可以重命名扩展文件

文件一:

package com.jason.kotlin.extension

fun <T> Iterable<T>.randomTake(): T = this.shuffled().first()

文件二:

import com.jason.kotlin.extension.randomTake as randomizer

fun main() {

    val list = listOf("Jason", "Jack", "Tom")
    val set = setOf("Jason", "Jack", "Tom")

    list.randomizer()
}

Kotlin标准库中的扩展

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

带接受者的函数字面量

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

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

带有接受者的函数字面量指的是 apply函数里面匿名函数带有一个接收者 T,T就是那个匿名函数的接收者。

具体讲解apply的原理如下:

// 这是一个扩展函数  
// 格式是  类型.函数() :返回值
// 扩展函数隐式自带了一个this 指向当前对象  
//  例如  count()  等价于   this.count()  相当于  字符串.count()
fun String.addExt() = "!".repeat(count())

//这是一个泛型的扩展函数
// 格式是 T.函数():返回值
fun <T> T.easyPrint(): Unit = println(this)

// 匿名函数
() -> Unit


// 分解调用过程
//1.定义扩展函数
fun File.ext(): Unit {
    setReadable(true)
}
//2.给block变量赋值
val block = File::ext
//3.传入apply函数
File("xx").apply { block }

案例如下:

import java.io.File

//扩展函数
fun String.addExt() = "!".repeat(count())

//泛型的扩展函数
fun <T> T.easyPrint(): Unit = println(this)

//为什么要传入扩展函数(泛型),而不是一个普通的匿名函数?
//T.() -> Unit
//扩展函数里自带了接收者对象的this隐式调用
//为什么是泛型的扩展函数?

//匿名函数,也可以是扩展函数
//普通的匿名函数
//() -> Unit
//匿名函数内部this指向一个File对象,隐式调用
//File.() -> Unit

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

//public inline fun File.apply(block: File.() -> Unit): File {
//    block()
//    return this
//}

fun main() {
    val file = File("xx").apply {
        setReadable(true)
    }

    //这里分解一下
    //1.定义扩展函数
    fun File.ext(): Unit {
        setReadable(true)
    }
    //2.给block变量赋值
    val block = File::ext
    //3.传入apply函数
    File("xx").apply { block }

}

函数式编程基础

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

DSL

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

在kotlin中,DSL是指领域特定语言(Domain Specific Language)的缩写,它是一种使用kotlin语言开发的,解决特定领域问题,具备独特代码结构的API。DSL可以让你用更简洁、更直观、更声明式的方式编写代码,例如:

  • 使用kotlinx.html库来构建HTML文档
  • 使用anko库来构建Android UI界面
  • 使用navigation组件来构建Android导航图

函数类别

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

变换

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

  • 最常用的两个变换函数是map和flatMap。

map变换

map变换函数会遍历接收者集合,让变换器函数作用于集合里的各个元素,返回结果是包含已修改元素的集合,会作为链上下一个函数的输入。

fun main() {
    val animals = listOf("zebra", "giraffe", "elephant", "rat")
    val babies = animals
        .map { animal -> "A baby $animal"}
        .map { baby -> "$baby,with the cutest little tail ever!" }
    println(animals)
    println(babies)

    val animalsLength = animals.map { it.length }
    println(animalsLength)

    //List<String>  List<List<String>>
}
//[zebra, giraffe, elephant, rat]
//[A baby zebra,with the cutest little tail ever!, A baby giraffe,with the cutest little tail ever!, A baby elephant,with the cutest little tail ever!, A baby rat,with the cutest little tail ever!]
//[5, 7, 8, 3]
  • 可以看到,原始集合没有被修改,map变换函数和你定义的变换器函数做完事情后,返回的是一个新集合,这样,变量就不用变来变去了。
  • 事实上,函数式编程范式支持的设计理念就是不可变数据的副本在链上的函数间传递。
  • map返回的集合中的元素个数和输入集合必须一样,不过,返回的新集合里面的元素可以是不同类型
flatmap

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

fun main() {
    val result = listOf(listOf(1,2,3), listOf(4,5,6)).flatMap { it }
    println(result)
}

过滤

过滤是函数式编程的第二大类函数,过滤函数接受一个predicate函数,用它按给定条件检查接收者集合里的元素并给出true或false的判定。如果oredicate函数返回true,受检元素就会添加到过滤函数返回的新集合里。如果predicate函数返回false,那么受检元素就被移出新集合。

fun main() {

    val result = listOf("Jack","Jimmy","Rose","Tom")
        .filter { it.contains("J") }

    println(result)


    val items = listOf(
        listOf("red apple", "green apple", "blue apple"),
        listOf("red fish", "blue fish"),
        listOf("yellow banana", "teal banana")
    )

    val redItems = items.flatMap { it.filter { it.contains("red") } }
    println(redItems)

}
找素数

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

fun main() {

    //除了1和它本身,不能被任何数整除的数
    //取模等于0,说明能够整除,如果没有一个是等于0的,说明是素数
    val numbers = listOf(7, 4, 8, 4, 3, 22, 18, 11)
    val primes = numbers.filter { number ->
        (2 until number).map { number % it }
            .none { it == 0 }
    }
    println(primes)
}

合并

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

zip

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

public infix fun <T, R> Iterable<T>.zip(other: Iterable<R>): List<Pair<T, R>> {
    return zip(other) { t1, t2 -> t1 to t2 }
}
fun main() {
    val employees = listOf("Jack", "Jason", "Tommy")
    val shirtSize = listOf("large", "x-large", "medium")
    val employeeShirtSizes = employees.zip(shirtSize).toMap()
    println(employeeShirtSizes["Jack"])

    //将每个元素值乘以3后累加起来。
    //folder
    val foldedValue = listOf(1, 2, 3, 4).fold(2) { accmulator, number ->
        println("Accmulator value:$accmulator")
        accmulator + (number * 3)
    }
    println("Final value:$foldedValue")


    val list = employeeShirtSizes.map { "${it.key},shirt size:${it.value}" }
    println(list)

}
fold

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

//将每个元素值乘以3后累加起来。
//folder
val foldedValue = listOf(1, 2, 3, 4).fold(2) { accmulator, number ->
    println("Accumulator value:$accmulator")
    accmulator + (number * 3)
}
println("Final value:$foldedValue")

为什么要学习函数式编程

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

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

序列

List、Set、Map集合类型,这几个集合类型统称为及早集合(eager collection)这些集合的任何一个实例在创建后,它要包含的元素都会被加入并允许你访问。对应及早集合,Kotlin还有另外一类集合:惰性集合(lazy collection)类似于类的惰性初始化,惰性集合类型的性能表现优异,尤其是用于包含大量元素的集合时,因为集合元素是按需产生的

Kotlin有个内置惰性集合类型叫序列(Sequence),序列不会索引排序它的内容,也不记录元素数目,事实上,在使用一个序列时,序列里的值可能有无限多,因为某个数据源能产生无限多个元素。

针对某个序列,你可能会定义一个只要序列有新值产生就被调用一下的函数,这样的函数叫迭代器函数,要定义一个序列和它的迭代器,你可以使用Koti的序列构造函数generateSequence,.generateSequencei函数接受一个初始种子值作为序列的起步值,在用generateSequence定义的序列上调用一个函数时,generateSequence函数会调用你指定的迭代器函数,决定下一个要产生的值。

fun Int.isPrime(): Boolean {
    (2 until this).map {
        if (this % it == 0) {
            return false
        }
    }
    return true
}

fun main() {
    //你想产生头1000个素数
    //假定0-5000之内,可以找到1000个素数
    val toList = (1..5000).toList().filter { it.isPrime() }.take(1000)
    println(toList.size)
//  生成与1000个素数,循环的终止条件是 take里面的数值
// 这样的代码实现表明,你不知道该检查多少个数才能得到整1000个素数,所有你用了5000这个预估数。但事实上5000个数远远不够,只能找出670个素数。
    val oneThousandPrimes = generateSequence(2) { value ->
        value + 1
    }.filter { it.isPrime() }.take(1000)
    println(oneThousandPrimes.toList().size)

}

Kotlin与Java互操作

案例目录结构如下:

image-20230325085213721

与Java类互操作

在kotlin里面可以直接通过类名来创建java对象

fun main() {
    val adversary = Jhava()
    println(adversary.utterGreeting())
}

在java里面默认使用kotlin的文件名来创建对象。

image-20230325085405160

如果想要重命名kotlin 的名字,则可以在kotlin文件开头使用@JvmName来进行重命名,这样java里面就可以使用我们自己命名的对象了

image-20230325085605346

互操作性与可空性

Java世界里所有对象都可能是null,当一个Kotlin函数返回String类型值,你不能想当然地认为它的返回值就能符合Kotlin关于空值的规定。

image-20230325084846105

@JvmField

在Java里,不能直接访问spells字段,所以必须调用getSpells,然而,你可以给Kotlin属性添加@vmField注解,暴露它的支持字段给Java调用者,从而避免使用getter方法。

class Spellbook {
//    @JvmField
    val spells = listOf("Magic Ms. L", "Lay on Hans")
}
  • 如果spells字段不加 @JvmField,调用的时候如下:

    • 需要使用getSpells()
    • image-20230325085931715
  • 如果加上

    • 则可以直接使用
    • image-20230325090134477

    在kotlin里面调用java字段

在kotlin里面不需要调用相关setter方法,你可以使用赋值语法来设置一个Java字段值了。

image-20230325085010341

@JvmOverloads

@JvmOverloads注解协助产生Kotlin函数的重载版本。设计一个可能会暴露给Java用户使用的APl时,记得使用@vmOverloadsi注解,这样,无论你是Kotlin开发者还是Java开发者,都会对这个API的可靠性感到满意。

例如在kotlin里面有一个方法有很多的参数,有些参数是有默认值的,这个时候在java里面调用的时候,必须把所有参数都附上值才能调用,如果想要只赋值一部分的参数就需要在kotlin里面给参数加上@JvmOverloads使得这个函数进行重载

//调用者可以指定英雄左手或右手拿什么食物,或者使用默认的配置——左手拿浆果,右手拿牛肉。
@JvmOverloads
fun handOverFood(leftHand: String = "berries", rightHand: String = "beef") {
    println("Mmmm... you hand over some delicious $leftHand and $rightHand")
}
public static void main(String[] args) {
     //Java要支持,重载
        Hero.handOverFood();
        Hero.handOverFood("apple");
        Hero.handOverFood("apple", "beef");
}

image-20230325090641906

@JvmStatic 主要针对于伴生对象的函数

@JvmField注解还能用来以静态方式提供伴生对象里定义的值。
@JvmStatic注解的作用类似于@JvmField,允许你直接调用伴生对象里的函数。

kotlin里面的定义如下:

class Spellbook {
    companion object{
        @JvmField
        val MAX_SPELL_COUNT = 10
        @JvmStatic
        fun getSpellbookGreeting() = println("I am the Great Grimoire!")
    }

}

java里面的调用如下:

//伴生对象
//        不加 @JvmStatic的写法
        Spellbook.Companion.getMAX_SPELL_COUNT();
        System.out.println(Spellbook.MAX_SPELL_COUNT);
//        加上之后的写法
        Spellbook.getSpellbookGreeting();

@Throws

抛出一个需要检查的指定异常,Java和Kotlin:有关异常检查的差异让@Throws注解给解决掉了,在编写供Java开发者调用的Kotlin APl时,要考虑使用@Throws注解,这样,用户就知道怎么正确处理任何异常了。

kotlin里面的定义:

@Throws(IOException::class)
fun acceptApology() {
    throw IOException()
}

Java里面的调用

try{
    Hero.acceptApology();
}catch (IOException e){
    System.out.println("Caught!");
}

Java中的函数类型

函数类型和匿名函数能提供高效的语法用于组件间的交互,是Koti编程语言里比较新颖的特性。他们简洁的语法因->操作符而实现,但Java8之前的DK版本并并不支持ambda表达式。在Java里,Kotlin函数类型使用FunctionN这样的名字的接口来表示的,FunctionN中的N代表值参数目。这样的Function接口由23个,从Function(0到Function22,每一个FunctionN都包含一个invoke函数,专用于调用函数类型函数,所以,任何时候需要调一个函数类型,都用它调用invoke。

kotlin里面定义一个匿名函数:

//添加一个translator的函数类型,接收一个字符串,
//将其改为小写字母,再大写第一个字符,最后打印结果。
val translator: (String) -> Unit = { utterance: String ->
    println(utterance.toLowerCase().capitalize())
}

java里面调用:

Function1<String, Unit> translator = Hero.getTranslator();
translator.invoke("TRUCE");
上一篇 下一篇