kotlin 入门

配置 gradle

build.gradle.kts 中配置阿里源

repositories {
    maven {
        url = uri("https://maven.aliyun.com/repository/public/")
    }
    mavenLocal()
    mavenCentral()
}

位运算

只能用于 IntLong

// 有符号左移,中缀表示法
println(1 shl 2) // 或者 1.shl(2)
// 有符号右移
println(4 shr 1) // 或者 4.shr(1)
// 无符号右移
println(4 ushr 1) // 或者 4.ushr(1)
// 位与
println(4 and 1) // 或者 4.and(1)
// 位或
println(4 or 1) // 或者 4.or(1)
// 位异或
println(4 xor 1) // 或者 4.xor(1)
// 位非
println(4.inv()) 

类型检测和类型转换

使用 is 检测符合类型,!is 检测不符合类型,该检测是在运行时进行。

val str:Any = "hello"
if (str is String) {
    println(str.uppercase())
}
if (str !is Number) {
    println("不是数字类型")
}

智能转换。

fun fn(x: Any) {
    // 在 && 之后 x 已被转化成 String 类型
    if (x is String && x.length > 0) {
        // x 转为 String 类型,只在该 if 范围有效
        println(x.uppercase())
    }
    // 报错,x 不是 String 类型
    // println(x.uppercase())
}
fun main() {
    fn("hello")
}

使用 as 实现类型转换,转换不成功会抛出异常。

fun fn(x: Any?) {
    // 报错,x 可能为 null,而 String 不为 null
    // val y = x as String
    val y = x as String?
    println(y)
}
fun main() {
    fn(null)
}

使用 as? 实现类型转换,转换不成功返回 nullasas? 都是在运行时转化。

fun fn(x: Any?) {
    val y = x as? String
    println(y)
}
fun main() {
    fn(null)
}

?:

val a = 1
val b = 2
// a 不为空 c 为 a,否则 c 为 b
val c = a ?: b
println(c)

if

if 用于判断,是一个代码块。

val a = 1
val b = 2
if (a > b) {
    println("${a} 大于 ${b}")
} else if (a < b) {
    println("${a} 小于 ${b}")
} else {
    println("${a} 等于 ${b}")
}

if 可以是一个表达式,它将返回语句中的最后一个值,此时必须有 else

val a = 1
val b = 2
val max = if (a > b){
    println("大于")
    a
} else {
    b
}
println(max)
val min = if (a < b) a else b
println(min)

when

c 语言中的 switch 类似,但功能更强大,,它的条件可以是任意表达式。

val a = 1
when(a) {
    1 -> println("x == 1")
    // 多行需要 {}
    2 -> {
        println("x == 2")
        println("ok")
    } 
    else -> {
        println("x != 1 and x != 2")
        println("fail")
    }
}

可以作为一个表达式使用,但此时必须有 else

val a = 2
val msg = when(a) {
    1 -> "x == 1"
    2 -> {
        println("x == 2")
        "ok"
    } 
    else -> {
        println("x != 1 and x != 2")
        "fail"
    }
}
println(msg)

使用 , 合并多个条件。

val a = 1
when(a) {
    1,2 -> println("x == 1 or x == 2")
    else -> println("other")
}

可以与 in!in 使用。

val x = 2
when(x) {
    in 1..3 -> println("1 <= x <= 3")
    // 条件匹配,便不会继续之后的匹配
    !in 4..6 -> println("x > 6 or x < 4")
    else -> println("other")
}

可以与 is!is 使用。

val x: Any = 8
when (x) {
    is String -> println(x.uppercase())
    is Int -> println(x * 2)
    else -> println("false")
}

when 可以不提供参数。

val x: Any = "tom"
when {
    x is String -> println(x.startsWith("a"))
    x is Int -> println(x * 2)
    else -> println("fail")
}

when 可以定义变量,该变量的作用域为 when 主体。

fun fn(x: String?): String? {
    return x?.lowercase()
}
fun main(args: Array<String>) {
    when (val x = fn("hEllo")) {
        is String -> println(x.uppercase())
        else -> println("nil")
    }
    // 错误
    // println(x)
}

for

对任何提供迭代器(iterator)的对象进行遍历。

val x = arrayOf(1,2,3)
for (it:Int in x) {
    print("$it ")
}
println()

通过索引遍历。

val x = arrayOf(1,2,3)
for (i:Int in x.indices) {
    print("${x[i]} ")
}
println()

使用 withIndex 获取索引和值。

val x = arrayOf(1,10,3,4)
for ((k,v) in x.withIndex()) {
    println("k: ${k}, v: $v ")
}
println()

while

c 语言中用法相同。

var x = 1
while (x < 4) {
    println(x)
    x++
}
do {
   println(x)
   x++
} while (x < 5)

break

跳出直接包围它的循环,用于 forwhile

fun main() {
    for (i in 1..10) {
        if (i == 4) {
            break
        }
        println(i)
    }
    println("over")
}

可以跳转到指定的标签处,标签是由标识符后紧跟 @ 符号构成。

fun main() {
    loop@ for (i in 1..10) {
        for (j in 1..10) {
            if (j == 3 && i == 2) {
                break@loop
            }
        }
        println(i)
    }
    println("over")
}

continue

终止本次循环,进行下一次的循环,用于 forwhile

fun main() {
    for (i in 1..10) {
        if (i % 2 == 0) {
            continue
        }
        print("$i ")
    }
    println("over")
}

continue 同样可以使用标签,只不过不会直接终止循环。

fun main() {
    loop@ for (i in 1..10) {
        print("$i ")
        for (j in 1..10) {
          	if (j % 2 == 0) {
                continue@loop
            }  
        }
    }
    println()
    println("over")
}

return

默认直接从包围它的函数或匿名函数处返回。

fun fn() {
    val a = arrayOf(1,4,2,3)
    a.forEach {
        if (it == 2) {
            // return 返回到 fn()
            return
        }
        println(it)
    }
}
fun main() {
    fn()
    println("main")
}

同样可以通过标签实现限定返回。

fun fn() {
    val a = arrayOf(1,4,2,3)
    a.forEach f@{
        if (it == 2) {
            // 与 continue 作用类似 
            return@f
        }
        println(it)
    }
    println("fn over")
}
fun main() {
    fn() // 1 4 3
    println("main")
}

可以通过隐式标签达到上面的效果,隐式标签与接受该 lambda 的函数同名。

fun fn() {
    val a = arrayOf(1,4,2,3)
    a.forEach {
        if (it == 2) {
            // 与 continue 作用类似 
            return@forEach
        }
        println(it)
    }
    println("fn over")
}
fun main() {
    fn() // 1 4 3
    println("main")
}

可以使用匿名函数来替代 lambda 表达式完成上述功能,此时不需要标签。

fun fn() {
    val a = arrayOf(1,4,2,3)
    a.forEach(fun (it){
        if (it == 2) {
            return
        }
        println(it)
    })
    println("fn over")
}
fun main() {
    fn() // 1 4 3
    println("main")
}

类型别名

为现有类型提供别名。

语法如下:

typealias 别名=类型

一个简单的示例:

typealias MyInt = Int
fun main() {
    val a: MyInt = 1
}

函数

使用 fun 定义函数。函数可以在文件顶层声明,也可以声明在局部作用域、作为成员函数以及扩展函数。

// fun 函数名(参数列表):返回类型
// Unit 表示无返回,可以省略
// 返回类型需要显示声明,除非返回的类型是 Unit 或单表达式函数
fun fn():Unit {
    println("这是一个函数")
}

使用 = 提供默认参数。

fun fn(a:Int = 1, b:Int=2):Int {
    return a + b
}

通过提供具名参数,摆脱函数调用时传参位置的限制。

fun fn(a: Int, b: Int) {
    println("$a + $b = ${a + b}")
}
fun main() {
    fn(1,2)
    fn(b=2,a=1)
}

当函数返回单个表达式时,可以使用如下的方式:

// 单表达式函数,返回类型可以自动推导
fun add(a: Int, b: Int) = a + b

vararg 修饰的参数称之为可变参数。可变参数只能有一个,且通常放在所有形参之后。

fun fn(a: Int,b: Int, vararg c: Int) {
    var sum = a + b
    println(c is IntArray) // true
    c.forEach {
        sum += it
    }
    println(sum)
}
fun main() {
    fn(1,2)
    fn(1,2,4,5) 
}

通过展开符 * 将一个数组传给可变参数。

fun fn(vararg c: Int) {
    var sum = 0
    c.forEach {
        sum += it
    }
    println(sum)
}
fun main() {
    fn(1, 2)
    // * 会将 IntArray 数组内容展开,每个元素是 Int 类型
    // IntArray 数组使用 toTypedArray() 转为 Array 数组 
    fn(1, 2, *intArrayOf(1, 2, 3))
    // Array 数组需要先转为 IntArray 数组,然后使用 *
    val c: Array<Int> = arrayOf(1,2,3)
    fn(1,2,*c.toIntArray())
}

中缀表示法

infix 修饰的函数可以使用中缀表示法,这种方式在之前位运算时有所体现。

中缀函数需要满足以下要求:

  • 必须是成员函数或扩展函数
  • 必须只有一个参数
  • 参数不能有默认值且不能是可变参数
infix fun Int.add(a: Int): Int {
    // 调用的对象
    return this + a
}
fun main() {
    val a = 1
    val b = 2
    val c = a.add(b)
    println(c)
    val d = a add b
    println(d)
}

函数类型

语法如下:

([[参数名:]参数类型,...,[参数名:]参数类型])->返回类型

一个简单的示例:

fun fn() {
    println("fn")
}
fun main() {
    // ::fn 函数引用
    val f:()->Unit = ::fn
    f()
}

箭头表示法是右结合的。

(Int)->((Int)->Int) 等价于 (Int) -> (Int) -> Int

如果函数类型可空,需要在外层添加圆括号。

((Int)->Int)?

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

typealias fn = (Int)->Int

函数引用

通过 :: 获取函数引用。

fun fn() {
    println("a")
}
fun main() {
    // 此时 f 指向了函数 fn 的内存空间,f 可以像函数 fn 一样使用 
    val f = ::fn
    f()
}

作用域函数

let

用于范围检查和空检查。

fun fn(msg: String?) {
    println(msg?.uppercase())
    println(msg?.lowercase())
    println(msg?.length)
}

可以看出每次调用字符串的方法或属性都要进行非空判断,这样就很麻烦,可以使用 let 解决。let 接收一个函数,该函数将调用对象作为参数传入,并将 let 体中的最后一个表达式作为结果返回。

fun fn(msg: String?): String? {
    return msg?.let {
        println(it)
        println(it.uppercase())
        println(it.lowercase())
        println(it.length)
        it.uppercase()
    }
}

run

let 功能类似,不同的是 run 接收的是调用对象扩展的无参且无返回的函数,run 块内部的 this 指向调用对象。

fun fn(msg: String?): String? {
    return msg?.run {
        println(this)
        println(uppercase())
        println(lowercase())
        println(length)
        uppercase()
    }
}

with

with 是一个非扩展函数,可以简单地访问其参数的成员,同时在引用其成员时可以省略实例名。

在使用 let 时,还需要每次都写 it,可以使用 with 来解决。

fun fn(msg: String?) {
    msg?.let {
        println(it)
        with(it) {
            // this 指向 it
            println(this)
        	println(uppercase())
       		println(lowercase())
        	println(length)
        }
    }
}

apply

在对象上执行代码块并返回对象本身,块内部的 this 指向对象本身,通常用于初始化对象。apply 接收的是调用对象扩展的无参且无返回的函数。

fun main(args: Array<String>) {
    val arr = mutableListOf<Int>()
    val t = arr.apply {
        // this 指向调用 apply 的对象
        println(this)
        this.add(6)
        // 调用对象方法可以省略 this
        add(4)
        add(2)
        add(1)
    }
    println(arr)
    println(arr === t) // true
}

also

apply 功能类似,不同的是 also 接收的是一个函数,该函数把调用对象作为参数传入。

fun main(args: Array<String>) {
    val arr = mutableListOf<Int>()
    val t = arr.also {
        println(it === arr)
        // it 不可以省略
        it.add(6)
        it.add(4)
        it.add(2)
        it.add(1)
    }
    println(arr)
    println(arr === t) // true
}

== 与 ===

== 使用 equals() 检测,表示结构相等;=== 两个引用指向同一个对象,表示引用相等。

// 类 A
class A
fun main() {
    val a = "abc hello"
    val b = "abc hello"
    println(a == b) // 翻译成 a?.equals(b) ?: b === null
    println(a != b)
    println("-".repeat(5))
    val o1 = A()
    val o2 = A()
    println(o1 === o2) // false
    println(o1 !== o2)
}

使用 class 声明,类默认是 public final 进行修饰的。

class Person {}

如果一个类没有类体,可以省略 {}

class Person

kotlin 不使用 new 关键字创建实例,像调用函数一样调用类就可以创建实例。

class Person{}
fun main() {
    val p = Person()
}

紧跟类名使用 constructor 定义的构造是主构造。

class Person constructor(var name: String, var age: Int) {   
}

如果主构造没有任何注解或可见性修饰符,则可以省略 constructor

class Person(var name: String, var age: age) {   
}
// 此时不能省略
class PersonFactory private constructor(){
}

默认提供一个空参的主构造。

// class Person(){} 
// 与上面的等价
class Person{
}

主构造不能包含代码,可以在 init 块中进行初始化工作。

// var 和 val 会向外部暴露该属性(提供 get 和 set 方法),不同的是 val 之后不能在修改
// 可以使用 private 进行修饰,这样属性就不会暴露
// class Rectangle(private var length:Double, var height:Double)
class Rectangle(var length:Double, var height:Double) {
    init{
        if (length < 0) {
            length = 0.0
        }
        if (height < 0) {
            height = 0.0
        }
        println("初始化...")
    }
}
val rec = Rectangle(10.0, 4.0)
println("length:${rec.length},height:${rec.height}")
// 修改
rec.length = 9.0

init 可以有多个,根据在类中出现的顺序依次完成初始化。

class Person {
    val beforeA = "初始化 beforeA".apply { println("before A") }
    init {
        println("A")
    }
    val beforeB = "初始化 beforeB".apply { println("before B") }
    init {
        println("B")
    }
}
fun main() {
    Person()
}

在类体中使用 constructor 定义的构造是次构造,可以有多个次构造,但每个次构造都将隐式或显示的调用(委托给)主构造。

class Person(var name: String, var age: Int) {
    // 委托给主构造
    constructor():this("", 0) {
        println("次构造")
    }
}
fun main() {
    Person()
}

对于 JVM,如果主构造上所有参数都有默认值,则编译器将会提供一个额外的无参构造。

class Person(var name: String = "", var age: Int = 0) {
}
fun main() {
    Person()
    Person("tom", 20)
}

一个类无主构造是指该类只提供次构造。

// 未提供 () 且有次构造,则此时无主构造
class Person{
    // 次构造
    constructor(name: String) {}
}
/*
// 有主构造
class Person() {
	constructor(name: String): this(){}
}
*/

可见性修饰符

类、对象、接口、构造函数、方法(或函数)与属性及其 setter 都可以有可见性修饰符,getter 的可见性总是与属性的可见性相同。kotlin 有四种可见性修饰符:privateprotectedinternalpublic,默认的可见性是 public。不同类型的声明作用域有不同的可见性修饰符。

函数、属性、类、对象与接口可以直接在包内的顶层声明。

package com.example.main
/*
public:默认可见性,声明随处可见
private:只在声明的文件内可见
internal:与声明所在的同一模块内随处可见
protected:不能用在顶层声明中
*/
// 这就是顶层声明
private val a = 1
internal var b = 2
var c = 3
	// setter 方法只在当前文件可见
	private set
// 错误
// protected var c = 3
private fun f() {}
internal class A{}
private interface B{}

类成员

private:成员只在该类内部可见,外部类同样不能访问内部类的 private 成员,但是内部类可以访问外部类的 private 成员。

class A {
    private var a = 1
    inner class B {
        private var b = 2
        init {
            // 可以访问
            println(a)
        }
    }
    init {
        // 错误
        // println(b)
    }
}
fun main() {
    // 错误,a 只在类 A 的内部可见
    // A().a
}

protected:成员只在该类内部和子类中可见。

// 如果使用了 protected,需要使用 open,不然 protected 作用等价于 private
open class A {
    protected var a = 1
}
class B: A() {
    init {
        println(a)
    }
}
fun main() {
    // 错误
    // A().a
    B()
}

internal:当前模块内都可见。
public:随处可见。

构造函数

/*
public: 默认
private:构造私有,只在本类可见
internal:同一模块可见
*/
class A private constructor() {
    init {
        println("1")
    }
    constructor(a: Int): this() {
        println(a)
    }
}

fun main() {
    // 错误
    // A()
    A(2)
}

局部声明

局部变量、函数与类不能有可见性修饰符。

类引用

与函数一样使用 ::。类引用是 KClass 对象,参看反射。

class A {
    init {
        println("init...")
    }
}
fun main() {
    val B = ::A
    val b = B()
}

如果类中的属性和方法使用 publicinternal 修饰,可以获取它的引用。

class A {
    var a: Int = 1
    protected var b: Int = 2
    private var c: Int = 3
    internal var d: Int = 4
}
fun main() {
    val a = A::a
    println(a.isFinal)
    // 错误
    // A::b
    // 错误
    // A::c
    val d = A::d
    println(d.visibility)
}

类中的属性

var 声明的属性是可变的,且提供默认的 gettersetter 方法;val 声明的属性是不可变的,且只提供默认的 getter 方法。

class A {
    var a: Int = 1
    val b: Int = 2
}
fun main() {
    val obj = A()
    println(obj.a)
    obj.a = 10
    println(obj.a)
    println(obj.b)
    // 报错,未提供 setter 方法
    // obj.b = 20
}

声明一个类的属性的完整语法如下:

var [: ] [= ]
    []
    []
或者
val [: ] [= ]
	[]

一般类体中的属性声明需要提供初始值。

class A {
    var a: Int = 1
    // 报错,需要初始化
    var b: Int
}

对于 val 声明的属性可以不需要初值,但需要 getter 函数。

class A {
    val a: Int
    	// 该函数特殊,不需要指定返回类型
    	get() {
           return 10 
        }
}
fun main() {
    val obj = A()
    println(obj.a)
}

对于 var 声明的属性,必须给出初值,无论是否给出 gettersetter 函数。

class A {
    // 报错
    var a: Int
    	get() {
            return 10
        }
}

通过 gettersetter 方法来限制属性的值。

class A(val a: Int, val b: Int) {
    var c: Int = 0
    	get() {
            return a * b
        }
    	set(value) {
            if (value > 0) {
                println("ok")
            } else {
                println("no")
            }
        }
}
fun main() {
    val obj = A(1,2)
    println(obj.c)
    obj.c = 10
}

gettersetter 中可以使用 field (只能在这两个函数中使用)引用幕后字段(可以理解成属性初值所在的内存) 。

class A{
    // a 的 getter 和 setter 默认提供的方式
    var a: Int = 0
    	get() = field
    	set(value) {
            // 不能使用 a = value 的方式,这样 a 只是调用了 set 方法,从而无限递归
            // field 引用了初值的内存地址
            field = value
        }
    var b: Int = 0
    	set(value) {
            if (b < 0) {
                field = 0
            } else {
                field = value
            }
        }
}
fun main() {
    val obj = A()
    println(obj.b)
    obj.b = -2
    println(obj.b)
}

幕后属性:帮助属性初始化和设置值,幕后属性使用 private 修饰,kotlin 不会为该属性提供 gettersetter 方法。

class A {
    // 通常将对应的属性名前加上 _ 作为幕后属性名
    private var _a = 1
    // 此时 _a 充当 field,不需要给出初始值,否则报错
    var a: Int
    	get() = _a
    	set(value) {
            if (value < 0) {
                _a = 0
            } else {
                _a = value
            }
        }
}
fun main() {
    val obj = A()
    println(obj.a)
    obj.a = 10
    println(obj.a)
}

使用 lateinit 可以延迟初始化属性。lateinit 的使用有以下的限制:

  • 只能修饰类体中使用 var 的属性。
  • 修饰的属性不能自定义 gettersetter 方法。
  • 修饰的属性必须是非空类型。
  • 不能修饰 IntByteShortLongFloatDoubleCharBoolean 类型的属性。
class A {
    lateinit var a: String
    // 报错
    // lateinit var b: String?
    // 判断是否已经初始化
    fun testAHasInitialized() = ::a.isInitialized
}
fun main() {
    val obj = A()
    // 报错,需要先初始化
    // println(obj.a)
    obj.a = "ok"
    println(obj.a)
    // 检测是否延迟初始化,需要 kotlin-reflect.jar
    // build.gradle.kts 中 implementation(kotlin("reflect"))
    // 类名::属性.isLateinit 的方式
    println(A::a.isLateinit)
    // 或者 实例名::属性.isLateinit 的方式
    println(obj::c.isLateinit)
}

继承

所有类都直接或间接继承一个超类 AnyAny 中只有三个方法:equals()hashCode()toString()

// 隐式继承 Any
class Person

如果一个类需要被继承,使用 open 修饰该类,被 open 修饰的类默认是 public

open class Person {}

使用 : 实现类的继承,我们称需要被继承的为父类,实现继承的类为子类,子类将继承父类中非 private 修饰的属性和方法。

// Person 为父类,Student 为子类
open class Person(var name: String, var age: Int)
// : 后面跟的是类的实例化语法
class Student:Person("",0) { 
    // 无次构造
}
fun main() {
    Student()
}

如果子类没有主构造,那么次构造需要通过 super 来显示初始化其父类,或者次构造通过次构造间接初始化父类。

open class Person(var name: String="", var age: Int=0)
// 无主构造
class Student: Person {
    // 显示 super
    constructor(name: String): super(name){}
    // 委托给次构造
    constructor(name: String, age: Int): this(name) {}
}

如果父类的成员和方法需要被子类重写,则父类的属性和方法需要使用 open 修饰,子类的方法需要使用 override 修饰。

open class Animal(name: String, age: Int) {
    open var version: String = "1.0"
    open fun run() {
        println("run...${version}")
    }
}
class Dog: Animal("tom", 3) {
    override var version: String = "2.0"
    // 使用 final 修饰, 则该方法将无法再被其子类重写
    final fun run() {
        println("${name} run...${version}")
    }
}
fun main() {
    val d = Dog()
    d.run()
}

在子类中使用 super 来访问父类的成员。

open class A(var a: Int, var b: Int) {
    open fun c() {
        println("$a + $b = ${a + b}")
    }
}
class B() : A(1, 2) {
    override fun c() {
        println("super.a=${super.a},super.b=${super.b}")
        super.c()
    }
}
fun main(args: Array<String>) {
    val b = B()
    b.c()
}

kotlin 不支持多继承

open class A {
}
open class B {
}
// 错误
class C : A(), B() {
}

通过 super<类名>. 的方式获取指定父类的方法。

open class A() {
    open fun c() {
        println("A")
    }
}
interface B {
    fun c() {
        println("B")
    }
}
// C 继承 A 并实现接口 B
class C : A(), B {
    override fun c() {
        // 调用 A 类的 c 方法
        super<A>.c()
        super<B>.c()
        println("C")
    }
}
fun main(args: Array<String>) {
    val c = C()
    c.c()
}

抽象类

使用 abstract 修饰的类叫抽象类,使用 abstract 修饰的方法叫抽象方法。

// 默认是 public 修饰
abstract class Animal {
    // 抽象属性
    abstract var version: Int
    // 一般属性
    var name = "Animal"
    // 抽象方法,需要子类实现
    abstract fun run()
    // 可以添加一般方法
    fun eat(food: String) {
        println(food)
    }
}
fun main() {
    // 错误,抽象类不能实例化
    // Animal()
}

抽象类可以有主构造和次构造。

abstract class A(a: String) {
    constructor():this("") {
        println("A 次构造")
    }
}
class B:A() { 
}
fun main() {
    val b = B()
}

接口

接口可以包含抽象方法和非抽象方法,与抽象类不同的是,接口无法保存状态。接口中的属性必须声明为抽象或提供访问器实现。

interface A {
	// 只声明的方法是抽象方法
    fun a()
    // 非抽象方法默认是 open 修饰
    fun b() {
    	println("非抽象方法")  
    }
    // 可以有私有的非抽象方法,但不能有私有的抽象方法
    private fun c() {
        println("私有方法")
    }
}
fun main() {
    // 报错,接口不能实例化
    // A()
}

接口不存在构造。

// 报错
interface A() {
}

一个类可以实现多个接口,多个接口用 , 隔开;一个接口可以继承多个接口。

interface A {}
interface B {}
interface C:A,B{}
class D:A,B{}

接口中的属性要么是抽象的,要么提供访问器的实现,但是不能进行初始化。

interface A {
    // 报错
    // var name: String = "A"
    // 对于 var 的属性,如果提供访问器,则 getter 和 setter 都要提供,同时不能使用 field
    // 因为没有初始化,因此一般不提供访问器
    var name: String
    val version: Int
        get() = 1
    fun a()
    fun b() {
        println("接口 A 中的 b 方法")
    }
}

一个类实现了一个或多个接口,则该类需要重写所有接口中的抽象属性和抽象方法。

interface A {
    var name: String
    val version: Int
        get() = 1
    fun a()
    fun b() {
        println("接口 A 中的 b 方法")
    }
}

class B: A {
    override var name: String = "name"
    override val version: Int = 1
    override fun a() {
        super.b()
        println()
    }
}

函数式接口

只有一个抽象方法的接口称之为函数式接口或单一抽象方法(SAM)接口,SAMSingle Abstract Method 的缩写。函数式接口可以有多个非抽象成员,但只能有一个抽象成员。

// 使用 fun 修饰接口
fun interface A {
    fun a()
}

对于非函数式接口的实现有一定的麻烦。

interface A {
    fun a()
}
fun main() {
    val obj = object:A{
        override fun a() {
            println("这是一个接口的实现")
        }
    }
    obj.a()
}

使用函数式接口可以将之简化。

fun interface A {
    fun a()
}
fun main() {
    val obj = A {
        println("这是一个函数式接口的实现")
    }
    obj.a()
}

扩展

可以为类或接口扩展。

class A {
    fun a() {
        println("a...")
    }
}
fun A.b() {
    // 类的扩展方法提供了 this,该 this 是调用该方法的对象
    println(this)
    println("b...")
}
fun main() {
    val obj = A() 
    obj.a()
    obj.b()  
}

子类可以拥有父类的扩展。

open class A {
    fun a() {
        println("a...")
    }
}
fun A.b() {
    println("b...")
    println(this)
}

class B: A() {
}
fun main() {
    val obj = B() 
    obj.a()
    obj.b()  
}

可以为可空类型扩展。

fun String?.fn() {
    println(this?.length ?: 0)
}
fun main() {
    var s: String? = "hello"
    s.fn()
    s = null
    s.fn()
}

可以为属性进行扩展,但有着以下限制:

  • 属性不能有初始值,意味着不能用 field 显示访问幕后字段。
  • val 的属性必须提供 getter 方法,var 的属性必须提供 gettersetter 方法。
class A(var a: Int, var b: Int) {
}
val A.sum: Int
	get() = this.a + this.b
var A.c: Int
    get() = a + b
    set(value) {
        a = value - b
    }
fun main() {
    val obj = A(1,2)
    println(obj.sum) // 3
    println(obj.c) // 3
    obj.c = 10
    println(obj.a) // 8
    println(obj.b) // 2
    println(obj.c) // 10
}

解构声明

一般的类需要为属性提供 componentN() (N 指的是解构的个数)方法,需要该方法使用 operator 进行修饰。

class Person(var name: String, var age: Int) {
    operator fun component1(): String {
        return this.name
    }
    operator fun component2(): Int {
        return this.age
    }
}
fun main() {
    val p = Person("tom", 18)
    // component1 对应 name,component2 对应 age
    val (name,age) = p
    println("name=${name},age=${age}")
}

可以使用 _ 忽略解构中某个不需要的变量。

class Person(var name: String, var age: Int) {
    operator fun component1(): String {
        return this.name
    }
    operator fun component2(): Int {
        return this.age
    }
}
fun main() {
    val p = Person("tom", 18)
    val (_,age) = p
    println("age=${age}")
}

对象表达式

语法格式如下:

object[:0-N个父类型]{}

对象表达式不能定义构造,但可以有初始化块;可以包含 inner 修饰的类,但不能包含嵌套类。

open class A(var a: Int, var b: Int) {}
fun main() {
    val obj = object: A(1,2) {
        init {
            println("可以有初始化块")
        }
        inner class B {}
        // 错误
        // class C {}
    }
    println(obj.a)
    println(obj.b)
}

对象声明

语法格式如下:

object objectName [:0-N个父类型]{}

对象声明可以包含嵌套类,但不能包含 inner 修饰的类;不能定义在函数和方法内;不能有构造,但可以有初始化块。

interface A {
    fun a()
}
object B: A {
    init {
        println("b...")
    }
    override fun a() {
        println("a...")
    }
    class C {
        init {
            println("c...")
        }
    }
    // 错误
    // inner class D {}
}
fun main() {
    B.a()
    B.C()
}

伴生对象

在类中使用 companion 修饰的对象声明称之为伴生对象。一个类至多定义一个伴生对象,可以通过 类名. 的方式访问伴生对象的内容。

class A {
    companion object B {
        var a = "b"
        fun c() {
            println("c...")
        }
    }
}
fun main() {
    println(A.B === A) // true
    println(A.B)
    println(A)
    println(A.a)
    println(A.B.a)
    A.c()
}

从上面的例子可以看出伴生对象名称有无都可以,对于无名称的伴生对象可以使用 类名.Companion 的方式。

class A {
    companion object {
        var a = "Companion"
        fun c() {
            println("c...")
        }
    }
}
fun main() {
    println(A.Companion === A) // true
    println(A.Companion)
    println(A)
    println(A.a)
    println(A.Companion.a)
    A.c()
}

可以为伴生对象进行扩展。

class A {
    companion object {
        var a = "Companion"
        fun c() {
            println("c...")
        }
    }
}
// Companion 不能省略,省略是为类 A 的对象添加扩展
fun A.Companion.fn() {
    println("伴生对象的扩展")
}
fun main() {
    A.fn()
}

嵌套类

类可以嵌套在其他类中,通过 类名. 的方式访问嵌套类,嵌套类不能访问外部类的成员,只能访问外部类中的其他嵌套类。

class A {
    var a = 1
    init {
        println("init...A")
    }
    class B {
        init {
            // 报错
            // println(a)
            println("init...B")
        }
    }
    class C {
        init {
            B()
            println("init...C")
        }
    }
}
fun main() {
    val a = A()
    // 实例 a 无法访问嵌套类
    val c = A.C()
}

类可以嵌套接口,接口也可以嵌套接口,接口也可以嵌套类。

class A {
    init {
        println("init...A")
    }
    interface B {
        fun fn()
    }
}

interface C {
    class D {
        init {
            println("init...D")
        }
    }
    interface E {
        fun fn()
    }
}

fun main() {
    val a = A()
    val b = object: A.B {
        override fun fn() {
            println("类A中的接口B")
        }
    }
    b.fn()
    val d = C.D()
    val e = object: C.E {
        override fun fn() {
            println("接口C中的接口E")
        }
    } 
    e.fn()
}

内部类

使用 inner 修饰的类,内部类可以访问外部类的成员,通过外部类的实例访问内部类。

class A {
    var a = 1

    init {
        println("init...A")
    }

    class B {
        init {
            println("init...B")
        }
    }
    // 内部类
    inner class C {
        init {
            println(a)
            B()
            println("init...C")
        }
    }
}

fun main() {
    val a = A()
   	// 只能通过实例访问内部类,A.C() 是错误的
    val c = a.C()
}

数据类

使用 data 修饰的类叫数据类,该类默认提供了以下功能

  • 所有属性的 getter (对于 var 还提供 setter) 方法。

  • equals()hasCode() 方法。

  • toString() 方法。

  • copy() 方法。

  • 提供所有属性的 component1()component2()等。

data class Person(var name:String, var age: Int)
fun main() {
    val (name,age) = Person("tom", 18)
    println("name=${name},age=${age}")
    val obj = Person("mike", 20)
    println(obj)
}

密封类

密封的类与接口提供了对继承的更多控制。密封类的所有直接子类在编译时是已知的,任何其他子类都不能出现在定义密封类的模块之外,密封接口也是如此。

使用 sealed 修饰类或接口。

sealed class A {}
sealed interface B {}

密封类和密封接口不能使用 open 修饰。

/*
// 错误
sealed open class A {}
*/
sealed class A {}
sealed interface B {}
// 继承密封类 A
class C: A() {}
// 实现密封接口 B
class D: B {}

密封类是不能实例化的,但可以有构造。构造只有两种可见性修饰:protected(默认)与 private

// 主构造默认 protected
sealed class A(a: Int) {
    constructor(): this(0) {
    }
    private constructor(a: Int, b: Int): this(a) {}
}
fun main() {
    // 错误
    // A()
}

密封类可以有抽象方法和属性。

sealed class A {
    abstract var a: Int
    abstract fun f()
}

密封类和其子类只能在同一模块的同一包内,密封接口也是一样的。

// package one
sealed class A{}
//-----------------
// package two
// 错误
class B: A(){}

间接子类不受此限制,除非其直接子类使用类 sealed

// package one
sealed class A{}
sealed class B: A(){}
open class C: A(){}
//-------------
// package two
class D: C() {}
/*
// 错误
class E:B(){}
*/

使用密封类的好处是在使用 when 作为表达式时,不需要 else 子句。

sealed class A {}
class B: A(){}
class C: A(){}
fun fn(a: A) = when(a) {
    is B -> println("B 类型")
    is C -> println("C 类型")
}

fun main() {
    fn(B())
}

枚举类

使用 enum 修饰,有着以下特点:

  • 枚举类可以实现一个或多个接口,且默认继承 kotlin.Enum 类,故不能再显示继承其他父类。
  • 枚举类不能使用 open,故枚举类没有子类。
  • 枚举类的构造只能使用 private 修饰,默认 private
  • 枚举类的所有实例必须在枚举类的第一行显示列出,多个实例使用逗号隔开,否则这个枚举类永远不能产生实例。
  • 使用 valueOf(value: String) 获取指定枚举值,使用 values() 获取所有枚举值组成的数组。

一个简单的示例:

enum class Season {
    // 如果实例之后没有其他代码 ; 可以不写
    SPRING,SUMMER,AUTUMN,WINTER;
}

或者

enum class Season {
    // 调用的是构造 SPRING()
    SPRING,
    SUMMER(),
    AUTUMN,
    WINTER;
}

使用有参构造,构造默认是 private

enum class Season(name: String = "春天") {
    SPRING,
    SUMMER("夏天"),
    AUTUMN("秋天"),
   	// ; 必须
    WINTER("冬天");
    init {
        println("init...$name")
    }
}

可以有抽象方法和抽象属性。

enum class Season {
    SPRING {
        override val no: Int = 1
        override fun fn() {
            println("春天")
        }
    },
    SUMMER() {
        override val no: Int = 2
        override fun fn() {
            println("夏天")
        }
    },
    AUTUMN {
        override val no: Int = 3
        override fun fn() {
            println("秋天")
        }
    },
    WINTER {
        override val no: Int = 4
        override fun fn() {
            println("冬天")
        }
    };

    abstract val no: Int
    abstract fun fn()
}

fun main() {
    val spring = Season.SPRING
    spring.fn()
    println(spring.no)
    // 表示第一个实例
    println(Season.WINTER.ordinal)
    // 实例名
    println(Season.WINTER.name)
}

可以实现接口。

interface Base {
    fun fn()
}
enum class Season : Base {
    // 调用的是构造 SPRING()
    SPRING {
        override fun fn() {
            println("spring")
        }
    },
    SUMMER {
        override fun fn() {
            println("summer")
        }
    },
    AUTUMN {
        override fun fn() {
            println("autumn")
        }
    },
    WINTER {
        override fun fn() {
            println("winter")
        }
    };
}
fun main() {
    Season.AUTUMN.fn()
}

类委托

类在实现接口时,将实现委托给其他已经实现该接口的类。委托对象的成员只能访问其自身对接口成员实现。

interface A {
    abstract var a: Int
    abstract fun f()
}
class B : A {
    override var a = 1
    override fun f() {
        println("a = $a")
    }
}
//使用 by 实现委托,类 C 通过 a 实现为没有重写的成员的重写
// C 没有重写 f(),则使用 a 的重写 f()
class C(a: A) : A by a {
    // B 中的 f 不会访问
	override var a = 2
}
fun main() {
    val b = B()
    val c = C(b)
    c.f() // a = 1
    // C 中的 a 值
    println(c.a)
}

属性委托

var 属性需要实现 ReadWriteProperty 接口,T 是拥有委托属性的对象类型,V 是属性类型。

import kotlin.properties.ReadWriteProperty
import kotlin.reflect.KProperty
class A : ReadWriteProperty<B, Int> {
    private var _v = 1
    // operator 可以省略, thisRef 是属性所在的对象,property 是属性的引用
    override operator fun getValue(thisRef: B, property: KProperty<*>): Int {
        println("getValue")
        return _v
    }
	// operator 可以省略,value 是新值
    override operator fun setValue(thisRef: B, property: KProperty<*>, value: Int) {
        println("setValue")
        _v = value
    }
}
class B {
    var a: Int by A()
}
fun main() {
    val b = B()
    // 调用 getValue
    println(b.a)
    // 调用 setValue
    b.a = 10
    println(b.a)
}

val 属性需要实现 ReadOnlyProperty 接口,T 是拥有委托属性的对象类型,V 是属性类型。

import kotlin.properties.ReadOnlyProperty
import kotlin.reflect.KProperty
class A : ReadOnlyProperty<B, Int> {
    private var _v = 1
    override operator fun getValue(thisRef: B, property: KProperty<*>): Int {
        println("getValue")
        return _v
    }
}
class B {
    val a: Int by A()
}
fun main() {
    val b = B()
    println(b.a)
}

属性委托也可以不实现上述接口,但对于 val 的属性需要提供 getValue() 函数,对于 var 的属性还需要提供 setValue() 函数。

import kotlin.reflect.KProperty
class A {
    private var _v = 1
    operator fun getValue(b: B, property: KProperty<*>): Int {
        return _v
    }

    operator fun setValue(b: B, property: KProperty<*>, value: Int) {
        _v = value
    }

}
class B {
    var a: Int by A()
}
fun main() {
    val b = B()
    println(b.a)
    b.a = 11
    println(b.a)
}

T 拥有委托属性的对象类型可以为空。

import kotlin.reflect.KProperty
class A {
    private var _v = 1
    operator fun getValue(nothing: Nothing?, property: KProperty<*>): Int {
        println("$nothing - $property")
        return _v
    }
    operator fun setValue(nothing: Nothing?, property: KProperty<*>, v: Int) {
        _v = v;
    }

}
var a: Int by A()
fun main() {
    println(a)
}

kotlin 提供了一个 lazy 函数,该函数接受一个 Lambda 表达式作为参数,并返回一个 Lazy 对象,该对象可作为实现延迟属性的委托,只能用于 val 属性。

val a: Int by lazy {
    println("lazy...")
    10
}
fun main() {
    println(a)
}

默认情况,lazy 属性求值是同步锁的:该值只在一个线程中计算,但所有线程都会看到相同的值。如果需要多个线程同时执行,提供 LazyThreadSafetyMode.PUBLICATION 参数。

// LazyThreadSafetyMode.PUBLICATION 初始化将总是发生在与属性使用位于相同的线程
val a: Int by lazy(LazyThreadSafetyMode.PUBLICATION) {
    println("lazy...")
    1
}
fun main() {
    println(a)
}

通过 kotlin 提供的 Delegates.observable() 实现属性的监听。该函数接受两个参数:第一个参数是初始值;第二个参数是修改时的处理程序。处理程序有三个参数:被赋值的属性、旧值与新值。

import kotlin.properties.Delegates

var a: Int by Delegates.observable(3) { property, oldValue, newValue ->
    println("$property - $oldValue - $newValue")
}

fun main() {
    println(a)
    // 每次赋值都将调用处理程序
    a = 2
}

通过 kotlin 提供的 Delegates.vetoable() 来决定是否允许赋值。

import kotlin.properties.Delegates

var a: Int by Delegates.vetoable(2) { property, oldValue, newValue ->
    when (newValue) {
        in 1..10 -> {
            println("$property - $oldValue - $newValue")
            // true 表示允许赋值
            true
        }
        else -> {
            println("值范围是 1-10")
            false
        }
    }
}
fun main() {
    println(a)
    a = 2
    println(a)
}

一个属性可以把它的 gettersetter 委托给另一个属性。这种委托对于顶层和类的属性(成员和扩展)都可用。

var a = 1
var b: Int by ::a

class A(var c: Int) {
    var d: Int by ::c
}

fun main() {
    println(b)
    val obj = A(2)
    println(obj.d)
}

可以使用映射实例自身作为委托来实现委托属性。

class A(map: Map<String, Int>) {
    val a: Int by map
    val b: Int by map
}

fun main() {
    val obj = A(mapOf("a" to 2, "b" to 3))
    println(obj.a)
    println(obj.b)
}

Map 只适用于 val 属性,对于 var 可以使用 MutableMap

class A(map: MutableMap<String, Int>) {
    var a: Int by map
    var b: Int by map
}
fun main() {
    val obj = A(mutableMapOf("a" to 2, "b" to 3))
    println(obj.a)
    println(obj.b)
}

集合

List

元素是有序的。

fun main() {
    // 读写
    val a: MutableList<Int> = mutableListOf(1, 2, 3)
    // 只读
    val b: List<Int> = listOf(1, 2, 3)
    a.add(4)
    println(a)
    println(b[0])
}

Set

元素是唯一且无序的。

fun main() {
    // 只读
    val a: Set<Int> = setOf(1, 1, 2, 3)
    // 读写
    val b = mutableSetOf<Int>(1, 1, 2, 3)
    println(a)
    b.add(3)
    println(b)
}

Map

存储的是键值对,键是唯一的。

fun main() {
    // 只读
    val a = mapOf(Pair("name", "tom"), Pair("age", 18))
    // to 是中缀函数,返回 Pair
    val b = mutableMapOf<String, Any>("name" to "tom", "age" to 18)
    println(a)
    // 添加键值对
    b["address"] = "earth"
    println(b)
    println(b["name"])
    for ((k, v) in a) {
        println("key=$k,value=$v")
    }
    b.forEach { (k, v) ->
        println("key=$k,value=$v")
    }
}

filter

过滤元素,并返回一个新的集合。

fun main() {
    val a = listOf(1, 2, 3)
    val b = setOf<Int>(4, 5, 6)
    val c = mapOf<Int, Int>(1 to 2, 2 to 3, 3 to 4)
    // 返回 List
    println(a.filter { it % 2 == 0 })
    // 返回 List
    println(b.filter { it % 2 == 1 })
    // 返回 Map
    println(c.filter { (k, v) -> (k + v) % 2 == 1 })
}

map

可以对元素进行新的映射。

fun main() {
    val a = listOf(1, 2, 3)
    val b = setOf<Int>(4, 5, 6)
    val c = mapOf<Int, Int>(1 to 2, 2 to 3, 3 to 4)
    // 返回 List
    println(a.map { if (it % 2 == 0) it + 1 else it - 1 })
    // 返回 List
    println(b.map { if (it % 2 == 0) "${it - 1}" else "${it + 1}" })
    // 返回 List
    println(c.map { (k, v) -> "${k + v}" })
}

reduce

对元素进行收集。

fun main() {
    val a = listOf(1, 2, 3)
    println(a.reduce { acc, i ->
        println("$acc,$i")
        acc + i
    })
}

fold

reduce 不同的是,fold 提供了一个初始值。

fun main() {
    val a = listOf(1, 2, 3)
    println(a.fold(-1) { acc, i ->
        println("$acc,$i")
        acc + i
    })
}

zip

中缀函数,将两个集合进行配对。

fun main() {
    val a = listOf(1, 2, 3)
    val b = setOf<Int>(4, 5, 6)
    val c = a zip b
    println(c)
}

any,all,none

any

只要有一个满足条件。

fun main() {
    val a = listOf(1, 2, 3)
    val b = setOf<Int>(4, 5, 6)
    val c = mapOf<Int, Int>(1 to 2, 2 to 3, 3 to 4)
    // 返回 Boolean
    println(a.any { it % 2 == 0 })
    // 返回 Boolean
    println(b.any { it % 2 == 0 })
    // 返回 Boolean
    println(c.any { (k, v) -> (k + v) % 2 == 0 })
}

all

所有都要满足条件。

fun main() {
    val a = listOf(1, 2, 3)
    val b = setOf<Int>(4, 5, 6)
    val c = mapOf<Int, Int>(1 to 2, 2 to 3, 3 to 4)
    // 返回 Boolean
    println(a.all { it % 2 == 0 })
    // 返回 Boolean
    println(b.all { it % 2 == 0 })
    // 返回 Boolean
    println(c.all { (k, v) -> (k + v) % 2 == 0 })
}

none

没有一个满足条件,与 all 相反。

fun main() {
    val a = listOf(1, 2, 3)
    val b = setOf<Int>(4, 5, 6)
    val c = mapOf<Int, Int>(1 to 2, 2 to 3, 3 to 4)
    // 返回 Boolean
    println(a.none { it % 2 == 0 })
    // 返回 Boolean
    println(b.none { it % 2 == 0 })
    // 返回 Boolean
    println(c.none { (k, v) -> (k + v) % 2 == 0 })
}

find,findLast

符合条件则返回元素,否则返回 null

find

返回第一个匹配条件的元素。

fun main() {
    val a = listOf(1, 2, 3)
    val b = setOf<Int>(4, 5, 6)
    println(a.find { it % 2 == 1 })
    println(b.find { it % 2 == 0 })
}

findLast

返回最后一个匹配条件的元素。

fun main() {
    val a = listOf(1, 2, 3)
    val b = setOf<Int>(4, 5, 6)
    println(a.findLast { it % 2 == 1 })
    println(b.findLast { it % 2 == 0 })
}

first,last

first,last

find 类似,不符合条件抛出 NoSuchElementException 异常。

firstOrNull,lastOrNull

find 类似。

count

fun main() {
    val a = listOf(1, 2, 3)
    val b = setOf<Int>(4, 5, 6)
    val c = mapOf<Int, Int>(1 to 2, 2 to 3, 3 to 4)
    // 返回 size
    println(a.count())
    // 指定条件的元素个数
    println(b.count {
        it % 2 == 0
    })
    println(c.count())
}

associateBy,groupBy

associateBy

将元素进行关联,返回 Map

fun main() {
    val a = listOf(1, 2, 3)
    val b = setOf<Int>(4, 5, 6)
    println(a.associateBy { "key:$it" })
    println(b.associateBy({ "key:$it" }, { it + 1 }))
    val c = mutableMapOf<String, Int>()
    println(b.associateByTo(c) { "key:$it" })
    val d = mutableMapOf<String, Int>()
    println(b.associateByTo(d, { "key:$it" }, { it + 1 }))
}

groupBy

根据条件分组,返回 Map

fun main() {
    val a = listOf(1, 2, 3)
    val b = setOf<Int>(4, 5, 6)
    println(a.groupBy { if (it % 2 == 0) "偶" else "奇" })
    println(b.groupBy({ if (it % 2 == 0) "偶" else "奇" }, { it + 2 }))
    val c = mutableMapOf<String, MutableList<Int>>()
    println(a.groupByTo(c) { if (it % 2 == 0) "偶" else "奇" })
    val d = mutableMapOf<String, MutableList<Int>>()
    println(a.groupByTo(d, { if (it % 2 == 0) "偶" else "奇" }) {
        it + 2
    })
    // 返回 Grouping
    val e = a.groupingBy { if (it % 2 == 0) "偶" else "奇" }
    // 获取 key(奇或偶)
    println(e.keyOf(1))
    // 输出 a 中元素
    e.sourceIterator().forEach(::println)
}

partition

根据条件划分。

fun main() {
    val a = listOf(1, 2, 3)
    // Set 也可以
    val (odd, even) = a.partition { it % 2 != 0 }
    println(odd)
    println(even)
}

flatMap

减少集合嵌套的层数。

fun main() {
    val a = listOf(1, 2, 3)
    val b = listOf(4, 5, 6)
    val c = listOf(listOf(a,b))
    println(c)
    println(c.flatMap {
        it.map {
            it
        }
    })
    println(c.flatten())
}

异常

所有异常类继承自 Throwable

使用 throw 抛出异常。

// Nothing 与函数结合表示永不返回,总是抛出异常
fun fn(): Nothing {
    throw Exception("error")
}
fun main() {
    fn()
}

通过 try-catch 来捕获异常。

fun main() {
    var a = 1
    try {
        a = a / 0
    } catch (e: ArithmeticException) {
        println(e.message)
    }
}

不管有无异常都将执行 finally,该语句块是可选的。

fun main() {
    var a = 1
    try {
        a = a / 0
    } catch (e: ArithmeticException) {
        println(e.message)
    } finally {
        println("over")
    }
}

try 不能单独存在,catchfinally 至少存在一个。

fun main(args: Array<String>) {
    var a = 1
    try {
        a = a / 0
    } finally {
        println("over")
    }
    // finally 仍会执行,但由于没有 catch,此句会因异常而不会执行
    println("kk")
}

反射

需要提供 kotlin-reflect.jar

dependencies {
    implementation(kotlin("reflect"))
}

你可能感兴趣的:(其他,kotlin,开发语言)