https://kotlinlang.org/

开发环境配置

本文采用win11系统进行开发环境配置,具体如下:

  • IntelliJ IDEA Ultimate Edition 2024.1.4

  • Azul Zulu Community™ 17.0.12

  • Kotlin 2.0.20

安装

IntelliJ IDEA

前往官网下载安装即可

https://www.jetbrains.com/idea/

JDK

安装完IntelliJ IDEA后新建空项目,之后打开右上角齿轮设置的项目结构

选择项目设置下的项目,找到SDK,点击选择下载JDK

选择17版本,Azul Zulu Community™,位置可以自定义,然后点击下载,再点击右下角的确定

界面右下角就会显示下载的进度条

安装完成可以回到设置查看是否有爆红,如下则为正常安装

也可以直接从官网安装:https://www.azul.com/downloads/?package=jdk#zulu


新建项目

IDEA新建项目,选择左侧的Kotlin,选择位置,构建系统选择Maven,JDK版本选择之前下载的azul-17,添加示例代码,高级设置里再设置下组ID和工作ID

点击创建,之后就会自动开始下载了(可能比较慢,需要霍格沃茨的网络环境)


代码结构

package com.xlyotut // 软件包名
​
/**
 * 主函数
 */
fun main() {
    // 变量声明和初始化
    val name = "Kotlin"
​
    // 打印输出
    println("Hello, " + name + "!")
​
    // 循环语句
    for (i in 1..5) {
        // 循环打印输出
        println("i = $i")
    }
}

输出:

Hello, Kotlin!
i = 1
i = 2
i = 3
i = 4
i = 5
  • 区分大小写,大小写敏感

  • 一般省略结尾的;号,多个语句在同一行则需要;进行分隔

  • 单行注释//,多行注释/**/


变量与基本类型

变量声明

变量语法:

var 变量名称 : 数据类型

不可变变量语法(赋值一次之后不能再更改):

val 不可变变量名称 : 数据类型 = 初始值

变量命名规范

  1. 使用有意义且易于理解的名字:变量名应该能够清楚地描述其代表的内容,避免使用缩写或含糊不清的名称。

  2. 使用驼峰式命名法

    • 对于局部变量和成员变量,使用小驼峰式命名法(lowerCamelCase),即第一个单词的首字母小写,后续单词的首字母大写。

    • 对于常量,使用大驼峰式命名法(UpperCamelCase),即所有单词的首字母都大写。

  3. 变量名应以字母开头:变量名可以包含字母、数字和下划线(_),但不能以数字开头。

  4. 尽量使用名词:因为变量代表的是数据,通常使用名词来命名。

  5. 避免使用 Kotlin 保留字:不要使用 Kotlin 语言中的保留字(如 val, var, fun 等)作为变量名。

以下是一些遵循 Kotlin 命名规范的例子:

// 成员变量
var companyName: String = "Example Inc."
​
// 常量
const val MAX_COUNT = 100
​
// 局部变量
val numberOfEmployees = 50
​
// 私有变量,通常在后面添加一个下划线
private var _isInitialized: Boolean = false

以下是一些非法的命名方式:

// 非法的命名:使用了缩写,不易理解
var cpyNm: String = "Example Inc."
​
// 非法的命名:以数字开头
var 123name: String = "John"
​
// 非法的命名:使用了 Kotlin 保留字
var val: Int = 10

数字类型

关键字

位数

符号

后缀

备注

Byte

8位

±

Short

16位

±

Int

32位

±

Long

64位

±

L

UByte

8位

无符号

0~255

UShort

16位

无符号

uU

0~65535

UInt

32位

无符号

uU

0~(2^32)-1

ULong

64位

无符号

uLUL

0~(2^64)-1

Float

32位

±

fF

6-7位小数位数

Double

64位

±

15-16位小数位数,小数默认为该类型

  • 数字可使用_进行分隔,如:114_5140

  • 无符号数在赋值时必须后边跟后缀uU


数字运算

运算就不用多说了,都是跟其它语言差不多,有2点需要注意

1.kotlin的位运算不是用符号,而是用函数来进行

如,异或运算

var num = 1 xor 2

函数对照表如下:

函数

作用

符号

shl

左移

<<

shr

右移

>>

ushr

无符号右移

>>

and

按位与

&

or

按位或

|

xor

异或

^

2.新增特有的区间运算符..

1到5(包括1和5)则如下所示

for (i in 1..5) {
    // 循环打印输出
    println("i = $i")
}

1到5(包括1,不包括5)则如下所示

for (i in 1..<5) {
    // 循环打印输出
    println("i = $i")
}

布尔类型

var isFirst : Boolean = true

值只能是truefalse


in判断

使用in判断一个数是否在一个区间里面

判断一个数是否在[1,5]里面

var num = 3
println(num in 1..5)

判断一个数是否在[1,5)里面

var num = 5
println(num in 1..<5)

判断一个数是否不在[1,5]里面

var num = 10
println(num !in 1..5)

判断一个数是否不在[1,5)里面

var num = 10
println(num !in 1..<5)

字符类型

字符使用单引号赋值

var ch : Char = 'a'
  • 无符号

  • 16位

  • 范围0~65535

  • Unicode存储

字符串使用双引号赋值

var str : String = "abcd"

三引号为多行字符串

var str : String = """
    abc
    ddd
    ooo
"""

输出如下

    abc
    ddd
    ooo cc

使用trimIndent可以去除每行开头的制表符

var str : String = """
    abc
       ddd
    ooo cc
""".trimIndent()

输出如下

abc
   ddd
ooo cc

模板字符串

语法

"$变量名称"

"${变量名称}"

如下示例

var idx = 114514
println("索引为$idx 个") // 输出:索引为114514 个

var idx = 114514
println("索引为${idx}个") // 输出:索引为114514个

等于判断

kotlin有2种比较运算符=====,这俩区别在于

  • ==结构相等

    • == 用于比较两个对象的内容是否相等。它调用了 equals() 方法(或者为基本数据类型比较它们的值)。

    • 对于基本数据类型(如 Int、Long、Float 等),== 比较的是值是否相等。

    • 对于引用数据类型(如类实例),== 比较的是两个对象是否代表相同的值(即它们的 equals() 方法返回 true)。

  • ===引用相等

    • === 用于比较两个对象的引用是否相同,即它们是否指向内存中的同一个对象。

    • 对于基本数据类型,=== 的行为与 == 相同,因为基本数据类型是按值传递的。

    • 对于引用数据类型,=== 检查的是两个引用是否指向同一个对象实例。

val a: Int = 1000
val b: Int = 1000
println(a == b) // 输出 true,因为值相等
println(a === b) // 输出 true,因为基本数据类型按值传递,这里比较的是同一个值
​
val str1 = "Hello"
val str2 = "Hello"
println(str1 == str2) // 输出 true,因为字符串内容相等
println(str1 === str2) // 在大多数情况下,对于字符串字面量,这也会输出 true,因为 Kotlin 有字符串字面量优化,但不是绝对的
​
val obj1 = StringBuilder("Hello")
val obj2 = StringBuilder("Hello")
println(obj1 == obj2) // 输出 false,因为它们的引用不同
println(obj1 === obj2) // 输出 false,因为它们不是同一个对象实例
​
val obj3 = obj1
println(obj1 === obj3) // 输出 true,因为 obj3 是 obj1 的引用

流程控制

其它流程控制语句都和java差不多,接下来说几点不一样的

when

类似于java的switch语句

when (idx) {
    214 -> println("214")
    666 -> println("666")
    else -> println("none")
}

这里的else相当于switchdefault

还可以直接做为表达式

var reslut = when (idx) {
    214 -> "214"
    666 -> "666"
    else -> "none"
}

做表达式就必须要有默认分支的else


for

与java的增强for循环类似,不过可以使用Range来表示范围

for (i in 1..5) {
    println(i)
} // 1 2 3 4 5

步长

for (i in 1..5 step 2) {
    println(i)
} // 1 3 5

倒序

for (i in 5 downTo 1) {
    println(i)
} // 5 4 3 2 1

函数

创建和使用

语法如下

fun 函数名称([函数参数列表..]): 返回值类型 {
    // 函数体
}

示例如下

fun helloWorld(msg: String, time: Int = 1) : String {
    return "[${msg}]: Hello World * $time"
}

像上面只有一行的简洁函数,可以使用表达式主体

fun helloWorld(msg: String, time: Int = 1) : String = "[${msg}]: Hello World * $time"

无返回值的函数默认返回值类型为Unit

fun funcTest(num: Int) : Unit {
    println(num)
}

变量属性函数

类似于C#语言,变量具有getset函数,默认生成如下

var num = 32
    get() = field
    set(value) {
        field = value
    }

其中field就是代表这个变量本身,不要在getset内部直接使用变量本身如num,使用field代替

其实就是名称为getset的函数,可以使用表达式主体=,也可以使用花括号{}来定义

  • get函数会在获取值时被调用,从而拿到该变量的值

  • set函数会在设置该值时被调用,从而设置变量的具体值,value就是目标值


变量函数

函数也可以被变量存储,类似于javascript

var helloWorld: (String, Int) -> String

就相当于

fun helloWorld(msg: String, time: Int): String

可以使用typealias给函数类型起别名

typealias Hello = (String, Int) -> String

给变量函数赋值有以下几种方法:

  • 方法引用

    fun main() {
        var helloWorld : Hello = ::helloWorldFunc // 方法引用
        var ctx = helloWorld("OK", 114)
        println(ctx)
    }
    ​
    fun helloWorldFunc(msg: String, time: Int): String {
        return "[${msg}]: Hello World * $time"
    }
  • 匿名函数

    fun main() {
        var helloWorld: Hello = fun(msg, time) = "[${msg}]: Hello World * $time"
        var ctx = helloWorld("OK", 114)
        println(ctx)
    }
  • lambda表达式

    fun main() {
        var helloWorld: Hello = { msg, time -> "[${msg}]: Hello World * $time" }
        var ctx = helloWorld("OK", 114)
        println(ctx)
    }

lambda表达式

其在只有一个传入参数的情况下,可不用为其变量命名,默认为it

var lambdaTest: (Int) -> Unit = { println(it) }

无显示return则最后一行默认为返回值

var lambdaTest: (Int) -> Double = {
    println(it)
    1.113
}

使用_可以简化不使用的变量,类似于C#

var helloWorld: (String, Int) -> String = { _, time -> "Hello World * $time" }

lambda中使用return需要打标签(定义了哪个函数需要提前返回)

var lambdaTest: (Int) -> Double = test@{ // test@定义标签
    println(it)
    return@test 1.113   // 使用标签返回
}

还可以

myTest { str, num ->
    println(str)
    return@myTest num.toFloat() // 定义了myTest函数需要提前返回
}

高阶函数

可以将变量函数做为参数传入和返回值(进行一个套的娃)

各位可以不看最后的输出,猜猜看会输出什么

fun main() {
    // 114为传入myTest的第一个参数
    var test = myTest(114) { str, num -> // 这里是定义func: (String, Int) -> Float函数
        println(str)
        num.toFloat() // 函数返回值
    }(514) // 这里是调用myTest返回的函数(Short) -> Double
    println(test) // 输出调用myTest返回的函数(Short) -> Double返回的值Double
}
​
fun myTest(time: Int, func: (String, Int) -> Float): (Short) -> Double {
    var floatVar = func("Call func", time)  // 调用传入函数
    var result: (Short) -> Double = { (floatVar + it).toDouble() } // 生成函数做为返回值
    return result   // 返回生成的函数
}

输出如下

Call func
628.0

内联函数

使用关键词inline声明内联函数,类似于C++的内联函数

直接将函数照搬照抄到目标位置,并不会进行调用函数

fun main() {
    myTest { println("test") }
}
​
inline fun myTest(func: () -> Unit) {
    func()
}

使用该网站https://www.decompiler.com/在线将生成的.class进行反编译

编译后(java)就相当于

public static final void main() {
    int $i$f$myTest = false;
    int var1 = false;
    System.out.println("test");
}

1.默认形参不能被当做变量

inline fun myTest(func: () -> Unit) {
    // var a = func // 这是不行的
    func()
}

2.使用noinline关键字可以指定非内联形参

inline fun myTest(noinline func: () -> Unit) {
    var a = func
    func()
}

3.高阶内联函数调用时return不用打标签,可以直接return,但因为是照搬照抄,可能会导致调用方提前返回

fun main() {
    myTest { return } // 提取返回
    println("hello kotlin") // 不会执行
}
​
inline fun myTest(func: () -> Unit) {
    func()
}

要解决的话打个标签就可以了

提前结束myTest函数

fun main() {
    myTest { return@myTest }
    println("hello kotlin")
}
​
inline fun myTest(func: () -> Unit) {
    func()
}

提取结束main函数

fun main() {
    myTest { return@main }
    println("hello kotlin")
}
​
inline fun myTest(func: () -> Unit) {
    func()
}
  • 内联函数最好用在有变量函数形参的函数(高阶函数)上


中缀函数

例如位运算的shl,在俩操作数中间

println(1 shl 2)

这个shl就是中缀函数

其本质就是一个函数,但是加上了infix关键字,要成为中缀函数有以下要求

  • 必须是成员函数

  • 只能有一个参数

  • 参数不能有默认值

定义

class Student(val name: String, inMajor: String) {
    var age: Int = 0
    lateinit var id: String
    var major: String = inMajor
​
    infix fun test(score: Float): String {
        return "${name}的成绩是${score}分"
    }
}

调用

fun main() {
    val student = Student("符玄", "仙舟·太卜司")
    student.id = "St19198"
​
    println(student test 114.5f)
}

输出

符玄的成绩是114.5分

需要注意的是

  • 中缀函数优先级低于算数运算符、类型转换和rangeTo运算符

  • 中缀函数优先级高于布尔运算符、isin以及其它一些运算符


类与对象

类定义

class Student public constructor(public val name: String, inMajor: String) {
    var age: Int = 0
    lateinit var id: String
    var major: String = inMajor
}
  • public constructor:直接在类名后面定义构造函数,有val或者var的才是这个类的成员变量

  • var age: Int:在类内部定义类的成员变量,必须进行初始化

  • lateinit var id: String:如果不想初始化,可以使用lateinit,在之后进行初始化,但在使用之前必须给其赋值

  • var major: String = inMajor:使用形参inMajor初始化成员变量major

上面这个类定义了一个Student

  1. 在实例化时必须首先传入name,且name之后不可改变

  2. 定义成员变量age,初始化为0

  3. 定义成员变量id,在之后自行初始化

  4. 定义成员变量major,通过实例化时传入的形参inMajor进行初始化

public constructor通常可以省略,如下所示

class Student(val name: String, inMajor: String) {
    var age: Int = 0
    lateinit var id: String
    var major: String = inMajor
}

辅助构造函数

相当于构造函数重载

class Student(val name: String, inMajor: String) {
    var age: Int = 0
    lateinit var id: String
    var major: String = inMajor
​
    constructor(name: String): this(name, "未知专业")   // 这里的this调用主构造函数
    constructor(): this("未知姓名")     // 辅助构造函数还可以调用其它辅助构造函数
}
  • 辅助构造函数不能定义成员变量(即不能使用valvar

  • 辅助构造函数可以有函数体,能执行一些语句

  • 执行辅助构造函数前会先调用主构造函数(辅助构造函数必须间接或直接调用主构造函数)

初始化代码块

在主构造函数执行时会自动调用该代码块,init代码块顺序按照从上往下执行,如果ageinit后面,则会报错变量未初始化

class Student(val name: String, inMajor: String) {
    var age: Int = 0
    lateinit var id: String
    var major: String = inMajor
​
    constructor(name: String): this(name, "未知专业")
    constructor(): this("未知姓名")
​
    init {
        println("${this.name}初始化")
        if (age > 18) age = 18
    }
}

成员函数

class Student(val name: String, inMajor: String) {
    var age: Int = 0
    lateinit var id: String
    var major: String = inMajor
​
    fun doWork() {
        println("${this.name}开始学习")
    }
}

类实例化

fun main() {
    val student = Student("符玄", "仙舟·太卜司")
    println(student.name)
    println(student.age)
    student.id = "St19198"
    println(student.id) // 因为lateinit关键字,使用该值前需要初始化
    println(student.major)
}

从c语言来讲,实例化类之后,变量存储的是该类对象的指针,指向具体的类对象

所以单纯的表示该类可以不使用var,使用val即可,类似于c语言的指针常量


运算符重载

operator fun plus(another: Student): Student {
    val result = Student(name, major)
    result.id = id + another.id
    result.age = age + another.age
    return result   // 返回一个新的Student对象
}

能重载的操作符表格

符号

函数名称

备注

+a

a.unaryPlush()

-a

a.unaryMinus()

!a

a.not()

a++或++a

a.dec()

有临时存储变量a0指定先后顺序

a--或--a

a.inc()

有临时存储变量a0指定先后顺序

a + b

a.plus(b)

a - b

a.minus(b)

a * b

a.times(b)

a / b

a.div(b)

a % b

a.rem(b)

a..b

a.rangeTo(b)

a in b

b.contains(a)

必须返回Boolean

a !in b

!b.contains(a)

必须返回Boolean

a > < >= <= b

a.compareTo(b)

返回值>0就是大于b,<0就是小于b,依此类推

a()

a.invoke()

小括号重载

a(i1, ..., n)

a.invoke(i1, ..., n)

小括号重载

a[i]

a.get(i)

索引访问获取重载

a[i] = b

a.set(i, b)

索引访问设置重载

  • +=-=*=/=%=这几个运算符要定义其xxxAssign运算符函数或运算符函数,2者选其一

    • 如,+=定义了函数plusAssign,则函数plus无需定义


空类型

可以参考C#的空类型?

var str: String? = null

表示这个变量可以为null

  1. 使用!!明确该对象不为空

val student: Student? = null
println(student!!.name)
  1. 使用?.进行null条件判断,安全调用

val student: Student? = null
println(student?.name)

如果对象为空,则安全调用返回null,且不会报错

  1. 使用?:自定义为null时的返回

val student: Student? = null
println(student?.name ?: "未知")

解构声明

类似于python,可以将一个类对象的一些属性直接解析出来

定义

class Student(val name: String, inMajor: String) {
    var age: Int = 0
    lateinit var id: String
    var major: String = inMajor
​
    operator fun component1(): String {
        return name
    }
​
    operator fun component2(): String {
        return major
    }
}

使用

fun main() {
    val stu = Student("符玄", "Type-C")
    var (name, major) = stu
    println("${name}的专业是$major")
}

相当于

fun main() {    
    val stu = Student("符玄", "Type-C")
    println("${stu.name}的专业是${stu.major}")
}

想使用该解构功能只需按照顺序重载运算符componentxxx函数即可,如下

operator fun component1(): 返回值类型 {
    // 函数体
}
​
operator fun component2(): 返回值类型 {
    // 函数体
}
​
operator fun component3...
operator fun component4...

还可以在lambda中使用

fun main() {
    val stu = Student("符玄", "Type-C")
    val func: (Student) -> Unit = { (name, major) ->
        println("${name}的专业是$major")
    }
    func(stu)
}

权限修饰符

关键词

作用

备注

public

公共访问

默认为该权限

internal

仅限同一模块访问

其它模块如第三方模块就不能使用

protected

仅自身和子类可访问

顶级声明不支持使用该权限

private

私有访问

仅同一作用域可访问


OOP

继承

kotlin中所有的类默认都是“终态”的,不能被任何类进行继承,加上open关键字后就可以被继承了(成为父类)

open class Student(val name: String, inMajor: String) {
    var age: Int = 0
    lateinit var id: String
    var major: String = inMajor
    
    constructor(name: String) : this(name, "未知专业")
    constructor() : this("未知姓名")
}

使用符号:即可进行继承,继承时需要调用父类的构造函数

open class Student(val name: String, inMajor: String) {
    var age: Int = 0
    lateinit var id: String
    var major: String = inMajor
​
    constructor(name: String) : this(name, "未知专业")
    constructor() : this("未知姓名")
}
​
open class ComputerStudent(name: String) : Student(name) {
    constructor() : this("计算机学生")
}
​
class SuperComputerStudent : ComputerStudent() {
}

需要注意的是:

  • 单一继承,不支持多重继承,只能继承一个父类

  • 可以继承多个接口,多个用符号,进行分隔

  • 子类辅助构造函数不能直接调用(super)父类构造函数

优先级关系:父类初始化 > 子类主构造函数 > 子类辅助构造函数


属性覆盖

子类可以使用关键词override重写父类成员

父类

open class Student(val name: String, inMajor: String) {
    open var age: Int = 0
    lateinit var id: String
    var major: String = inMajor
​
    open fun code() {
        println("打代码")
    }
​
    constructor(name: String) : this(name, "未知专业")
    constructor() : this("未知姓名")
}

子类

class ComputerStudent(name: String) : Student(name) {
    override var age: Int = 16
        get() = super.age
        set(value) {
            field = if (value > 18) 18 else value
        }
​
    constructor() : this("计算机学生")
​
    override fun code() {
        println("计算机学生[${name}]在打代码")
        super.code()    // 调用父类的方法
    }
}

需要注意的是:

  • 成员也是默认“终态”的,不能被子类重写,需要添加关键字open

  • 可以重写的成员可以是成员变量和成员函数

  • 对与open属性,在父类的初始化代码块init中使用会默认用子类的,而子类那时还未初始化,这样就会出现空指针

    open class Student {
        open val name: String = "ABC"
    ​
        init {
            println("我的名称是:${this.name.length}")    // 未初始化,会出现空指针
        }
    }
    ​
    class ComputerStudent : Student() {
        override val name: String = "123"
    }

类型判断和转换

使用关键字is进行类型判断,使用as进行类型转换

fun main() {
    val student = ComputerStudent("银狼")
    studentDoWork(student)
}
​
fun studentDoWork(student: Student) {
    if (student is ComputerStudent) {
        student.code()  // idea提示智能转换,将student智能转化为ComputerStudent
    }
    else if (student is SportStudent) {
        (student as SportStudent).sport()
    }
}

处理可空类型时,使用as?可以进行安全转换,如果转换失败则返回null

val stu : Student? = SportStudent()
val su = stu as? ComputerStudent
println(su)

顶层Any类

类似于java的Object超类

public open class Any public constructor() {
    public open operator fun equals(other: kotlin.Any?): kotlin.Boolean
    public open fun hashCode(): kotlin.Int
    public open fun toString(): kotlin.String
}
  • 运算符==就是调用函数equals

  • 可空对象进行==运算时实际是这样的

    a == b
    a?.equals(b) ?: (b === null)

抽象类

使用abstract关键字定义抽象类,不需要open关键词即可被其它类继承

abstract class Student(val name: String, inMajor: String) {
    abstract var age: Int
    lateinit var id: String
    var major: String = inMajor
​
    abstract fun doWork()
​
    constructor(name: String) : this(name, "未知专业")
    constructor() : this("未知姓名")
}
​
class SportStudent : Student() {
    override var age: Int = 0
        get() = field
        set(value) { field = value }
​
    override fun doWork() {
        println("唱跳rap篮球")
    }
}
​
open class ComputerStudent(name: String) : Student(name) {
    constructor() : this("计算机学生")
​
    override var age: Int = 16
        get() = field
        set(value) { field = value }
​
    override fun doWork() {
        println("计算机学生打代码")
    }
}

需要注意的是:

  • 抽象类可以继承非抽象类

  • 抽象类的抽象成员子类必须重写,除非子类也是抽象类


接口

使用interface关键字定义接口

interface IPlayGame {
    fun playGame(name: String)
    var x: String   // 接口不能存储某个具体的值,所以只能重写getter,不能重写setter
    fun playGame2(name: String) {   // 接口方法的默认方法
        x = name    // 具体setter还是要看具体实现类
        println("玩$name")
    }
}
​
abstract class Student(val name: String, inMajor: String) {
    abstract var age: Int
    lateinit var id: String
    var major: String = inMajor
​
    abstract fun doWork()
​
    constructor(name: String) : this(name, "未知专业")
    constructor() : this("未知姓名")
}
​
open class ComputerStudent(name: String) : Student(name), IPlayGame {
    constructor() : this("计算机学生")
​
    override var age: Int = 16
        get() = field
        set(value) { field = value }
​
    override fun doWork() {
        println("计算机学生打代码")
    }
​
    override fun playGame(name: String) {
        println("计算机学生${this.name}在玩$name")
    }
    
    override var x: String = "" // 实现类就可以正常存储和重写
        get() = field
        set(value) { field = value }
}

实现的接口同时有同名的方法,使用super<接口类型>.方法名称即可区分开来

interface A {
    fun sleep() = println("A sleep")
}
​
interface B {
    fun sleep() = println("B sleep")
}
​
class Sub : A, B {
    override fun sleep() {
        super<B>.sleep()    // 调用接口B的sleep函数
    }
}

需要注意的是:

  • 实现类相当于继承了接口,所以可以使用isas关键字进行类型判断和转换

  • 接口不能存储某个具体的值,所以只能重写getter,不能重写setter,实现类就可以正常存储和重写

  • 接口的方法可以有默认方法,对接口属性进行的操作还是要看具体实现类的gettersetter


类的扩展

类似与C#对类的扩展,一种无侵入式的对类进行功能扩展

fun main() {
    val str = "克拉拉"
    println(str.exTest())
}
​
fun String.exTest() : String {
    return "Hello $this"
}

还可以扩展属性,只不过不能直接扩展,因为直接扩展不进行任何的存储,需要扩展属性的gettersetter函数才能看起来跟真的属性一样

var field: Int = 114514     // 让该变量替代A.myNum去存储
var A.myNum: Int
    get() = field
    set(value) { field }
  1. 名称冲突时:

class A {
    fun hello() = "A Hello"
}
​
class B(private val a: A){
    private fun A.test() {
        hello()         // 优先匹配被扩展函数,A里的hello
        this.hello()    // 扩展函数里的this还是A的对象,还是A里的hello
        [email protected]()  // 打上具体标签指定具体的同名函数,B里的hello
    }
    
    fun hello() = "B Hello"
}
  1. 扩展方法还可以做为变量函数使用

fun main() {
    val func: String.(Int) -> String = { this + it.toString() }
    println("abc".func(96))     // 输出:abc96
    println(func("abc", 96))     // 输出:abc96
}
  1. 同名扩展函数优先使用对应类型的扩展函数

fun main() {
    val obj : A = B()
    obj.exTest()    // 输出:A
}
​
open class A
class B : A()
​
fun A.exTest() = println("A")
fun B.exTest() = println("B")
  1. 扩展方法内部调用被扩展类的成员受到权限修饰符控制

open class A
class B : A() {
    private fun foo(){}
}
​
fun B.exTest() {
    // foo()    // 非法,私有函数
}

需要注意的是:

  • this表示当前操作的类对象

  • 语法就是要扩展的类名.额外方法名称

  • 扩展类本身就有的函数需要进行重载,如果一模一样则扩展时就会失效

  • 同样受到权限修饰符的控制

  • 扩展可以被子类重写


泛型

语法

对于泛型类型只能当做Any类型来使用

fun main() {
    Score<Float>(1.14f).showScore()
}
​
class Score<T>(var score: T) {
    fun showScore() = println("分数为${score}")
}

使用函数类型的形参时,我们可以使用泛型来代表不确定的类型

// 接受Int类型形参,返回值类型为T的func函数
// 整个test函数返回类型T
fun <T> test(func: (Int) -> T): T {
    // ...
}
​
// 无形参无返回值的func函数为T类型的扩展函数
// 整个test函数返回类型T
fun <T> test(func: T.() -> Unit): T {
    // ...
}

上述的函数类型 T.() -> Unit意思是传递给这个函数的lambda表达式或函数字面量将会在 T 类型的实例上执行(T类型的扩展函数)

需要注意的是:

  • 不能使用泛型来创建对象

  • 子类在继承时,可以选择将父类的泛型参数给明确为某一类型,或是使用子类定义的泛型参数作为父类泛型参数的实参使用


内置高阶扩展函数

作用是用来优化繁琐的代码

不止如上这些,还有通过非对象调用的高阶扩展函数


apply

如下代码,给可空类型进行赋值,看起来十分的不优雅

class Person(var name: String, var age: Int) {
    fun hello() = println("Hello, $name, $age")
    constructor(): this("", 0)
}
​
fun makePeople(people:Person?) : Person? {
    // 不优雅的赋值
    people?.age = 500
    people?.name = "纳西妲"
    people?.hello()
    return people
}

可使用内置的高阶函数apply进行赋值

fun makePeople(people: Person?): Person? = people?.apply {
    age = 500
    this.name = "纳西妲"
    hello()
}

这个apply函数可使用this操作调用者people?,且代码块结束自动返回调用者people?

其实就相当于给people?类进行了扩展,增加了个扩展函数,如下

{
    age = 500
    this.name = "纳西妲"
    hello()
}

然后apply函数里自动调用这个扩展函数

来看看剩下的


let

用于执行一个lambda表达式并将得到的结果作为返回值返回

fun letMy(people: Person): Person = people.let { 
    it.name = "纳西妲"
    it.age = 500
    it
}

源码如下

/**
 * Calls the specified function [block] with `this` value as its argument and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#let).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.let(block: (T) -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block(this)
}

also

用于执行一个lambda表达式并返回对象本身,跟apply功能一样,但采用的是it参数形式传递给lambda当前对象

fun letMy(people: Person): Person = people.also {
    it.name = "纳西妲"
    it.age = 500
}

源码如下

/**
 * Calls the specified function [block] with `this` value as its argument and returns `this` value.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#also).
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.also(block: (T) -> Unit): T {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    block(this)
    return this
}

run

用于执行一个lambda表达式并将得到的结果作为返回值返回,它跟apply一样,使用this传递当前对象,可以看到接受的参数是一个扩展函数

val obj = Any()
var result = obj.run {
    println(this)
    "Hello world!"
}   // result存储的结果为:Hello world!

源码如下

/**
 * Calls the specified function [block] with `this` value as its receiver and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#run).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> T.run(block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return block()
}

takeIf

满足某些条件才进行处理,传入一个用于判断的函数,根据结果返回对象本身或是null

val obj = Any()
var result = obj.takeIf { false } ?: "Noop"
println(result) // Noop

源码如下

/**
 * Returns `this` value if it satisfies the given [predicate] or `null`, if it doesn't.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#takeif-and-takeunless).
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeIf(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (predicate(this)) this else null
}

takeUnless

takeIf相反,传入一个用于判断的函数,根据取反结果返回对象本身或是null

val obj = Any()
var result = obj.takeUnless { true } ?: "Noop"
println(result) // Noop

源码如下

/**
 * Returns `this` value if it _does not_ satisfy the given [predicate] or `null`, if it does.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#takeif-and-takeunless).
 */
@kotlin.internal.InlineOnly
@SinceKotlin("1.1")
public inline fun <T> T.takeUnless(predicate: (T) -> Boolean): T? {
    contract {
        callsInPlace(predicate, InvocationKind.EXACTLY_ONCE)
    }
    return if (!predicate(this)) this else null
}

with

手动传入一个现有变量,然后通过这个变量去调用传入的lambda

var len = with("Hello Kotlin") { this.length }
println(len)    // 12

源码如下

/**
 * Calls the specified function [block] with the given [receiver] as its receiver and returns its result.
 *
 * For detailed usage information see the documentation for [scope functions](https://kotlinlang.org/docs/reference/scope-functions.html#with).
 */
@kotlin.internal.InlineOnly
public inline fun <T, R> with(receiver: T, block: T.() -> R): R {
    contract {
        callsInPlace(block, InvocationKind.EXACTLY_ONCE)
    }
    return receiver.block()
}

逆变和协变

类似于C#的逆变和协变:https://www.cnblogs.com/VVStudy/p/11404300.html

众所周知,父类可以存储子类对象,如下

val n1: Int = 114
val n2: Number = n1

但是使用泛型如此会报错

class Test<T>(var data: T)
​
fun myTest() {
    val o1: Test<Int> = Test(114)
    val o2: Test<Number> = o1
}

如此这般,泛型类型Test<T>存在以下几种形变:

  • 协变:因为IntNumber的子类,所以Test<Int>也是Test<number>的子类,可以直接转换

  • 逆变:跟协变相反,Test<number>可以直接转化为Test<Int>,前者是后者的子类

  • 抗变Test<Int>Test<Number>没半毛钱关系,无法互相转换

诶!那么现在我们遇到的情况就是抗变,在kotlin中,泛型默认都是抗变的

使用俩关键字可以进行转换

  • out:标记一个类型参数为协变,可以实现子类转父类

  • in:标记一个类型参数为逆变,可以实现父类转子类

val o1: Test<Int> = Test(114)
val o2: Test<out Number> = o1

现在,Test<Number>类型可以接收Test<Int>类型了

还有通配符*代表所有的,且不受限制,但只能当做Any?类型使用

var test: Test<*> = Test(114)
test = Test("Hello")

需要注意的是:

  • out只允许读出,是生产者,不能用作函数的参数,setter被限制

  • int只允许写入,是消费者,不能用作函数的返回值,getter被限制


泛型界限

可以对泛型指定界限,以达到限制某一类型及其子类的目的

class Test<T: Number>(var data: T)

现在该类的data类型只能是Number及其子类

多个约束使用where关键字

class Test<T>(var data: T) where T: Number, T: Comparable<T>

写C#的小伙伴肯定很熟悉了


类型擦除

在编译后的代码是不会有任何的泛型类型的,都会被替换为Any?,如果定义了上界,则会替换为上界类型

基于此,很多操作是不允许的,比如类型判断

class Test<T>(private var data: T) {
    fun isType(obj: Any): Boolean {
        // return obj is T  // 报错
    }
}

包括使用

val test: Test<Int> = Test(10)
println(test is Test<Double>)   // 报错,由于类型擦除,无法判断具体的类型
println(test is Test)

使用reified关键字可以具体化类型,具化类型参数允许在函数体内部检测泛型类型,该关键字只适用于内联函数

inline fun <reified T> isType(value: Any): Boolean {
    return value is T
}

数组

kotlin中数组的类型是Array


创建

可使用内置的函数arrayOf()arrayOfNulls()以及emptyArray()

val arr: Array<Int> = arrayOf(1, 2, 3, 4, 5)

使用Array构造函数创建

val arr: Array<Int> = Array(5) { 0 }    // 创建容量为5的数组,并全部初始化为0

创建完成后,数组容量和元素类型是固定不变的


操作

跟java的Type[]一样,都是差不多的

val arr: Array<Int> = arrayOf(1, 2, 3, 4, 5)
arr[4] = 114
println(arr[4]) // 114

循环

for (i in arr) println(i)

indices获取数组的索引

for (index in arr.indices) {
    println(arr[index])
}

withIndex方法能同时获取元素和索引

for ((index, value) in arr.withIndex()) {
    println("$index : $value")
}

forEach方法使用lambda来遍历

arr.forEach { println(it) }

forEachIndexed方法带索引的lambda来遍历

arr.forEachIndexed { index, i -> println("$index -> $i") }

joinToString将数组元素拼接为字符串

println(arr.joinToString("-", "(", ")", 4, "...more"){
    (it * it).toString()
})  // (1-4-9-16-...more)

还有其它操作

val arr: Array<Int> = arrayOf(1, 2, 3, 4, 5)
val arr2: Array<Int> = arr.copyOf()
​
// 相等
println(arr === arr2)               // false
println(arr.contentEquals(arr2))    // true
​
// 分隔数组
println((arr + arr2).sliceArray(0..<8).joinToString())    // 1, 2, 3, 4, 5, 1, 2, 3
​
// 数组反转排序
arr.reverse()
// 数组打乱随机排序
arr.shuffle()
// 数组排序
arr.sort()
arr.sortDescending()
// 自定义类需重写compareTo接口方法
​
// 包含
println(8 in arr2.reversedArray())  // false
println(arr.contains(3))            // true

可变长参数

使用关键字vararg指定可变长参数,0~多个

fun main() {
    myTest(1, 2, 3, 4, 5, 6, 7, 8)
}
​
fun myTest(vararg nums: Int) {
    println(nums.contentToString())     // [1, 2, 3, 4, 5, 6, 7, 8]
}

可当做数组使用

但是不能直接传入数组,需要使用扩展运算符*来将数组里的每个元素一个个传入

fun main() {
    val arr = arrayOf(1, 2, 3, 4, 5, 6, 7, 8)
    myTest(*arr.toIntArray())
}
​
fun myTest(vararg nums: Int) {
    println(nums.contentToString())     // [1, 2, 3, 4, 5, 6, 7, 8]
}

或者引用元素类型

fun main() {
    val arr = arrayOf("abc", "123", "IIVI")
    myTest(*arr)
}
​
fun myTest(vararg values: String) {
    println(values.contentToString())     // [abc, 123, IIVI]
}

需要注意的是:

  • 只允许一个可变长参数

原生类型数组

类型

java

BooleanArray

boolean[]

ByteArray

byte[]

CharArray

char[]

DoubleArray

double[]

FloatArray

float[]

IntArray

int[]

LongArray

long[]

ShortArray

short[]

直接创建原生数组

intArrayOf(1, 2, 3, 45)
floatArrayOf(1f, 2f, 3f, 4f, 5f)
... // 依此类推

多维数组

val arr: Array<IntArray> = arrayOf(intArrayOf(1, 2, 3), intArrayOf(4, 5), intArrayOf(6, 7, 8, 9))

如下

{
    {1, 2, 3},
    {4, 5},
    {6, 7, 8, 9}
}

访问

println(arr[1][1])  // 5

访问非定义索引会报错

println(arr[1][2])  // 第二行第3个,不是默认0,而是未定义直接报错

进行对比

val arr: Array<IntArray> = arrayOf(intArrayOf(1, 2, 3), intArrayOf(4, 5), intArrayOf(6, 7, 8, 9))
val arr2 = arrayOf(intArrayOf(1, 2, 3), intArrayOf(4, 5), intArrayOf(6, 7, 8, 9))
​
println(arr.contentEquals(arr2))        // false
println(arr.contentDeepEquals(arr2))    // true

contentEquals只会对比第一层元素,而contentDeepEquals可以对比深层的元素


集合

  • List:有序集合,可包含重复颜色

  • Set:不包含重复元素的集合,一般不维护顺序

  • Map:一组键值对

创建

使用函数mutableXxxOf来创建不同类型的集合

val list = mutableListOf(1, 2, 3, 4, 5)

操作

集合

val set1 = mutableSetOf("AAA", "BBB", "CCC")
val set2 = mutableSetOf("DDD", "CCC", "FFF")
​
// 并集∪(+)
println(set1 union set2)                    // [AAA, BBB, CCC, DDD, FFF]
// 交集∩
println(set1 intersect set2)                // [CCC]
// 差集-(-)
println(set1 subtract set2)                 // [AAA, BBB]
// 并集减去交集
println((set1 - set2) union (set2 - set1))  // [AAA, BBB, DDD, FFF]

对于自定义类对象,需要重写equalshashCode函数才能在集合中不重复存放

class MyClass(var name: String, var age: Int) {
    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false
​
        other as MyClass
​
        if (name != other.name) return false
        if (age != other.age) return false
​
        return true
    }
​
    override fun hashCode(): Int {
        var result = name.hashCode()
        result = 31 * result + age
        return result
    }
​
    override fun toString(): String {
        return "MyClass(name='$name', age=$age)"
    }
}
​
val set = mutableSetOf(
    MyClass("符玄", 18), MyClass("纳西妲", 500),
    MyClass("符玄", 18), MyClass("符玄", 300)
)
println(set)
// 输出: [MyClass(name='符玄', age=18), MyClass(name='纳西妲', age=500), MyClass(name='符玄', age=300)]

键值对

使用to关键字可以创建一个键值对

val map = mutableMapOf<Int, MyClass>(
    10001 to MyClass("符玄", 18),
    10002 to MyClass("纳西妲", 500),
    19198 to MyClass("符玄", 18),
    99999999.to(MyClass("符玄", 300))
)
println(map)
// 输出: {10001=MyClass(name='符玄', age=18), 10002=MyClass(name='纳西妲', age=500), 19198=MyClass(name='符玄', age=18), 99999999=MyClass(name='符玄', age=300)}
val my: MyClass? = map[19198]
println(my)     // MyClass(name='符玄', age=18)

遍历

map.forEach { (k, v) ->
    println("${k} = ${v}")
}

进行覆盖时会返回被移除的值

val old: MyClass? = map.put(10002, MyClass("藿藿", 50))

花式移除

map -= 10002                // 移除单个
map -= listOf(10001, 19198) // 批量移除

获取的键值对不存在时,返回默认值

map.getOrDefault(114514, MyClass("1919810", 114514))

获取键值对,如果不存在则存入默认值并返回

map.getOrPut(114514) { MyClass("1919810", 114514) }

需要注意的是:

  • 值被移除时,那个键值对就会整个被移除,不存在值不在但键存在的情况

  • 重写了+=-=运算符用于putremove


迭代器

集合和数组都有迭代器

val iterator = map.values.iterator()

Iterable迭代器接口

/**
 * Classes that inherit from this interface can be represented as a sequence of elements that can
 * be iterated over.
 * @param T the type of element being iterated over. The iterator is covariant in its element type.
 */
public interface Iterable<out T> {
    /**
     * Returns an iterator over the elements of this object.
     */
    public operator fun iterator(): Iterator<T>
}

可以看到是operator运算符重载,重载的运算符就是for in遍历

不能在for in遍历时修改集合,会报异常ConcurrentModificationException

val list = mutableListOf(1, 2, 3, 4, 5)
for (i in list) {
    println(i)
    list.remove(i)    // 不能在for in遍历时修改集合
}

可以使用迭代器来修改,但是还是不能插入

val list = mutableListOf(1, 2, 3, 4, 5)
val iterator: MutableIterator<Int> = list.iterator()
while (iterator.hasNext()) {
    println(iterator.next())
    iterator.remove()
}
println(list.count())   // 0

需要注意的是:

  • 迭代器的使用是一次性的,要从头来过则需要重新调用iterator()来重新生成迭代器


序列

类似于java的Stream流,是惰性的,只有需要时才被调用

val list = listOf("AAA", "BBB", "CCC", "DDD", "EEE", "FFF", "FF", "123")
val result = list.asSequence().filter {
    println("正在过滤:$it")
    it.length > 2
}.map {
    println("正在转换小写操作")
    it.lowercase()
}.take(4)
​
result.forEach { println(it) }

其只包含一个生成迭代器的函数

/**
 * Given an [iterator] function constructs a [Sequence] that returns values through the [Iterator]
 * provided by that function.
 * The values are evaluated lazily, and the sequence is potentially infinite.
 *
 * @sample samples.collections.Sequences.Building.sequenceFromIterator
 */
@kotlin.internal.InlineOnly
public inline fun <T> Sequence(crossinline iterator: () -> Iterator<T>): Sequence<T> = object : Sequence<T> {
    override fun iterator(): Iterator<T> = iterator()
}

需要注意的是:

  • 对于大量数据使用序列可优化性能,而对于特别少的数据来说可能会增加开销


特殊类型

数据类型

使用关键字data创建一个数据类型,代表一个数据类,类似于java的lambok@Data注解

data class Milk(val name: String, var price: Double) {
    var brand: String = ""  // 该成员变量不会被data处理
}

声明该数据类后,会自动生成以下函数:

  • equals()/hashCode()

  • toString()

    • 生成的字符串格式类似于Milk(name=纯牛奶, price=13.6)

  • componentN()

    • 按成员声明顺序进行解构函数声明

  • copy()

    • 用于对对象进行拷贝

val milk = Milk("纯牛奶", 13.6)
val milk2 = Milk("纯牛奶", 13.6)
println(milk == milk2)          // true
​
val milkCopy = milk.copy()
milkCopy.price += .2233
println(milkCopy)    // Milk(name=纯牛奶, price=13.8233)

编译后如下(Java):

public final class Milk {
   @NotNull
   private final String name;
   private double price;
​
   public Milk(@NotNull String name, double price) {
      Intrinsics.checkNotNullParameter(name, "name");
      super();
      this.name = name;
      this.price = price;
   }
​
   @NotNull
   public final String getName() {
      return this.name;
   }
​
   public final double getPrice() {
      return this.price;
   }
​
   public final void setPrice(double var1) {
      this.price = var1;
   }
​
   @NotNull
   public final String component1() {
      return this.name;
   }
​
   public final double component2() {
      return this.price;
   }
​
   @NotNull
   public final Milk copy(@NotNull String name, double price) {
      Intrinsics.checkNotNullParameter(name, "name");
      return new Milk(name, price);
   }
​
   // $FF: synthetic method
   public static Milk copy$default(Milk var0, String var1, double var2, int var4, Object var5) {
      if ((var4 & 1) != 0) {
         var1 = var0.name;
      }
​
      if ((var4 & 2) != 0) {
         var2 = var0.price;
      }
​
      return var0.copy(var1, var2);
   }
​
   @NotNull
   public String toString() {
      return "Milk(name=" + this.name + ", price=" + this.price + ')';
   }
​
   public int hashCode() {
      int result = this.name.hashCode();
      result = result * 31 + Double.hashCode(this.price);
      return result;
   }
​
   public boolean equals(@Nullable Object other) {
      if (this == other) {
         return true;
      } else if (!(other instanceof Milk)) {
         return false;
      } else {
         Milk var2 = (Milk)other;
         if (!Intrinsics.areEqual(this.name, var2.name)) {
            return false;
         } else {
            return Double.compare(this.price, var2.price) == 0;
         }
      }
   }
}

需要注意的是:

  • 该数据类型必须定义至少一个成员变量

  • 主构造函数必须标记为valvar

  • 如果以及实现了上述自动实现的函数,就不会自动生成,除了componentNcopy函数不能显示实现

  • 该数据类型不能被继承、不能是抽象、不能是密封的、不能是内部的


枚举类型

kotlin中,每个枚举值可以看做一个个类,它们都继承与定义的枚举类,如下

enum class EHttpStatus {
    OK,                
    MovePermanently,
    NotFound,
    InternalServerError,
}

OK这个枚举值就是一个类,其继承与EHttpStatus这个类

可以看到上述的枚举是个Http状态码类,只有状态没有码,枚举也是可以有成员的,我们给它加个码

enum class EHttpStatus(val code: Int) {
    OK(200),
    MovePermanently(301),
    NotFound(404),
    InternalServerError(500),
}

还能定义函数和重写函数

enum class EHttpStatus(val code: Int) {
    OK(200),
    MovePermanently(301),
    NotFound(404),
    InternalServerError(500);
​
    override fun toString(): String {
        return code.toString()
    }
}

甚至每个枚举值还能实现接口

interface IShowInfo {
    fun showInfo(): String
}
​
enum class EHttpStatus(val code: Int): IShowInfo {
    OK(200) {
        override fun showInfo(): String {
            return "OK"
        }
    },
    MovePermanently(301) {
        override fun showInfo(): String {
            return "Move Permanently"
        }
    },
    NotFound(404) {
        override fun showInfo(): String {
            return "Not Found"
        }
    },
    InternalServerError(500) {
        override fun showInfo(): String {
            return "Internal Server Error"
        }
    };
​
    override fun toString(): String {
        return code.toString()
    }
}

使用when表达式进行判断

var status = EHttpStatus.MovePermanently
val msg = when (status) {
    EHttpStatus.MovePermanently -> "1"
    EHttpStatus.OK -> "2"
    EHttpStatus.NotFound -> "3"
    EHttpStatus.InternalServerError -> "4"
}
println(msg)

如下图就可以看到枚举类和枚举值都分别生成了一个类


匿名类

适用于以匿名的形式创建一个临时的使用对象,在使用完之后就不需要了的情况,比如函数的接口形参

fun main() {
    makeAShow(object : IShowInfo {
        override fun showInfo(): String {
            return "Hello Kotlin"
        }
    })  // Hello Kotlin
}
​
interface IShowInfo {
    fun showInfo(): String
}
​
fun makeAShow(show: IShowInfo) {
    println(show.showInfo())
}

匿名类不能创建构造函数,所以也称对象表达式,无法定义成员变量

fun main() {
    println(test().name)
    // println(test().age)  // 报错,没有age这个成员变量
}
​
open class Human(val name: String)
​
fun test() = object : Human("青衣") {
    val age: Int = 18
    override fun toString(): String = "Human($name)"
}

对于只有一个函数的单一接口,也称函数式接口,可以直接使用lambda写函数体

makeAShow { "Hello World" } // Hello World

它还是个饿汉式单例

fun main() {
    val sing = Singleton
    println(sing.name)   // 你干嘛~?
    println(sing)        // 你干嘛~?诶哟~
}
​
// 单例类
object Singleton {
    var name = "你干嘛~?"
    override fun toString(): String = "${name}诶哟~"
}

编译如下(Java):

public final class Singleton {
   @NotNull
   public static final Singleton INSTANCE = new Singleton();
   @NotNull
   private static String name = "你干嘛~?";
​
   private Singleton() {
   }
​
   @NotNull
   public final String getName() {
      return name;
   }
​
   public final void setName(@NotNull String var1) {
      Intrinsics.checkNotNullParameter(var1, "<set-?>");
      name = var1;
   }
​
   @NotNull
   public String toString() {
      return name + "诶哟~";
   }
}

伴生对象

希望一个类即支持单例类那样直接调用,又支持像一个普通类一样使用,那就使用伴生对象

实际就是将单例类写到某个类的内部

fun main() {
    println(MyClass.MOCK)
    MyClass.isDone = true
    println(MyClass.isDone)
}
​
class MyClass(var name: String){
    companion object {
        const val MOCK = "MOCK_STR"
        var isDone = false
    }
}

可以在伴生对象里定义静态的成员函数和成员变量,就是相当于java的静态关键字static

使用类名来进行访问


委托模式

是一个更好的继承替代方案

委派设计模式代码如下

fun main() {
    val derived = Derived(BaseImpl(114514))
    derived.print()     // 114514
}
​
interface Base {
    fun print()
}
​
class BaseImpl(val x: Int) : Base {
    override fun print() = print(x)
}
​
class Derived(val b: Base) : Base {
    override fun print() = b.print()
}

可以看到Derived对于print方法无能为力,只能委托其它已经实现的类BaseImpl来进行print

kotlin中,对于该设计模式给予了原生支持

class Derived(b: Base) : Base by b
  1. 除了类,类的成员属性也可以委托给其它对象

class MyClass {
    val name: String by lazy { "延迟名称" }
}

上述代码表示在获取name时才会触发后面函数体的内容

  1. 还可以对变量进行监控

import kotlin.properties.Delegates
​
fun main() {
    val my = MyClass()
    println(my.name)    // 我是初始值
    my.name = "新的值"   // name changed from 我是初始值 to 新的值
}
​
class MyClass {
    var name: String by Delegates.observable("我是初始值") {
        property, oldValue, newValue ->
        println("${property.name} changed from $oldValue to $newValue")
    }
}
  1. 使用::来将属性委托给其它属性

class MyClass(var name: String = "") {
    var nick: String by ::name
}
  1. 委托给map集合,实现属性名称一一对应属性值

fun main() {
    val map = mutableMapOf<String, Any>(
        "name" to "青衣",
        "age" to 18
    )
    val user = User(map)
    println(user)       // User(name='青衣', age=18)
    map["age"] = 9999   // 属性名称一一对应属性值
    println(user)       // User(name='青衣', age=9999)
}
​
class User(map: MutableMap<String, Any>) {
    var name: String by map
    var age: Int by map
    override fun toString(): String {
        return "User(name='$name', age=$age)"
    }
}

密封类

密封类只能同一模块进行继承和使用,使用关键字sealed定义密封类

sealed class MyClass

密封类本身就是抽象类,所以不能实例化


异常

自定义异常

class TestException(override val message: String): Exception(message)

异常处理

fun main() {
    try {
        test(1, 0)
    } catch (e: TestException) {
        println("程序发生错误: ${e.message}") // 程序发生错误: 你干嘛~?诶哟除以0
    } finally {
        println("程序结束") // 程序结束
    }
}
​
class TestException(override val message: String): Exception(message)
​
fun test(a: Int, b: Int): Int {
    if (b == 0) throw TestException("你干嘛~?诶哟除以0")
    return a / b
}

还可以在变量赋值时进行简写

val input = readln()
val a: Int? = try { input.toInt() } catch (e: NumberFormatException) { null }

规则,就是用来打破的( ̄へ ̄)!