文章

Kotlin基础——类与对象

开始

使用class关键字声明一个类,类中包含构造器、初始化块、方法、属性、内部类

1
class Obj // 创建一个空类

kotlin中没有new关键字,直接使用函数形式创建对象

1
var obj: Obj = Obj()

访问修饰符

kotlin的访问修饰符有public、protected、private、internal,可用于类、接口、属性、构造器、方法

默认权限都为public

private和internal

  • private:在同一模块中可见,可跨文件
  • internal:只在同一文件中可见

构造器

kotlin的构造器分为主构造器和次构造器,当没有声明构造器时,kotlin会自动生成一个空参构造

主构造器

主构造器跟在类名之后

1
2
3
4
5
6
7
class Person constructor(name: String, age: Int,) {
    // 构造器参数列表可以使用尾部逗号
}
// 当主构造器没有注解以及访问修饰符(默认为public)时,constructor关键字可以省略
class Person(name: String, age: Int,) {
    // 主构造器中的参数可以直接在init块和属性初始化表达式中使用
}

init块:主构造器中不能包含代码,初始化代码可在init中编写,一个类中可以包含多个init块

init块实际是主构造器的一部分,因此主构造器和init块先于次构造器执行

1
2
3
4
5
class Person(name: String) {
    init {
        println(name)  // 可以直接使用参数
    }
}

在构造器中传入的参数为形参,类中可以保存参数

1
2
3
4
5
6
7
8
class Person(name: String) {
    var name: String = name
}
// 可以直接在构造器参数列表中声明
class Person(var name: String, val age: Int) {
    // Person类拥有属性name
    // age为只读属性
}

次构造器

一个类中可以拥有多个次构造器,使用constructor关键字声明

当类不存在主构造器时,一个次构造器可以委托给另一个次构造器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Person {
    var name: String = ""
    var age: Int = 0
    var sex: String = ""
    constructor(name: String) {
        this.name = name
    }
    constructor(name: String, age: Int) {
        this.name = name
        this.age = age
    }
    // 委托给另一个构造器
    constructor(name: String, age: Int, sex: String) : this(name, age) {
        this.sex = sex
    }
}

若类存在主构造器,则每个次构造器都需要委托给主构造器

1
2
3
4
5
6
7
8
9
10
11
class Person(var name: String) {
    var age: Int = 0  // 次构造器不能承担初始化的任务
    // 每个次构造器必须委托到主构造器,通过this引用到指定构造器
    constructor(name: String, age: Int) : this(name) {
        this.age = age
    }
    // 通过其他次构造器间接委托到主构造器
    constructor(name: String, age: Int, salary: Double) : this(name, age) {
        // ...
    }
}

属性定义

kotlin中没有static关键字,属性支持访问修饰符、var、val修饰

1
2
3
4
5
class Person(var name: String, var age: Int,) {
    var id: String = "$name:$age"
    // 只读属性,必须立即初始化或在init块中初始化
    val realAge: Int = age
}

属性的初始化时机

kotlin类中的每个属性必须进行初始化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Person(var name: String, var age: Int) {
    // 直接赋值
    var attr: String = "value1"
    
    // 使用构造参数
    var attr1: String = "$name:$age"
    
    // init块
    var attr2: String
    init {
        attr2 = "value2"
    }
    
    // lateinit延迟初始化
    // 属性必须为var,属性不能是Int、Float、Double、Char、Boolean类型
    lateinit var attr3: String
    // 使用lateinit后可以在任何时候初始化,若在未初始化时访问,会抛出异常
    
    // by-lazy延迟初始化
    // 属性必须为val,在第一次访问属性时初始化
    val attr4: String by lazy {
        // ...
        // expression
    }
}

getter、setter

kotlin会为每个属性自动生成默认的getter和setter,在访问属性obj.attr时自动调用getter和setter

  • val属性不允许有setter
  • 在属性声明之后可以设置自定义getter
  • 使用自定义getter后,属性不能通过lateinit和by-lazy进行初始化
  • getter的访问权限与属性的访问权限相同,setter的访问权限可单独设置
1
2
3
4
5
6
7
8
9
10
class Person(var name: String, var age: String) {
    var attr: String
    	get() {
            // ...
            return "$age"
        }
    	set(value) {
            // ...
        }
}

幕后字段field:用于在getter和setter中使用当前字段的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class Person(var age: Int) {
    var id: String = ""
    	get() {
            return field
        }
    	set(value) {
            field = value
        }
    
    var id1: String
    	get() {
            return id1 // 直接调用id1字段会自动调用getter,此时产生无限递归
        }
}
  • 当只有自定义getter时
    • 无论是否使用field,属性支持直接赋值和init块初始化
  • 当只有自定义setter时
    • 无论是否使用field,属性只支持直接赋值初始化
  • 当自定义getter和setter都存在时
    • 若getter和setter中都没有使用field,属性可以不初始化
    • 否则,属性必须直接赋值初始化

简写getter:对于getter可以使用单表达式写法

1
2
3
4
class Person(var name: String) {
    var id: String = ""
    	get() = "value"  // expression
}

属性的类型推断:当属性的类型可以从赋值简写getter中推断时,类型声明可以忽略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class Person(var name: String) {
    // 从直接赋值中推断
    var age = ""
    	get() = field
    
    // 从单表达式getter中推断
    var age
    	get() = ""
    init {
        // 属性必须初始化
        age = "..."
    }
    
    // 编译错误,只能从直接赋值和单表达式getter其中一个进行推断
    var age = ""
    	get() = ""
    
    // 语法错误,不存在field
    var age
    	get() = field
}

继承

kotlin的所有类都继承Any类,Any类相当于java的Object,但是对equals、hashCode、toString三个方法都进行了重写

kotlin类默认为final类,不可继承,类属性默认为不可重载

使用open关键字修饰一个类,使该类可以被继承

当子类继承父类时,必须通过父类的构造器构造父类(主构造器和次构造器都可以)

  • 当子类存在主构造器时,使用主构造器构造父类
  • 当子类不存在次构造器时,将每个次构造器委托到父类的构造器构造父类
1
2
3
4
5
6
7
8
9
10
11
12
13
open class Person(var name: String) {
    
}
// 子类存在主构造器
class Student(name: String) : Person(name) {
    // 子类继承父类的name属性
}
// 子类不存在子构造器
class Teacher : Person {
    constructor(name: String) : super(name) {
        // 委托到父类构造器
    }
}

子类重写

kotlin类属性默认为不可重写,子类不可声明父类的同名属性和同名方法

在属性和方法声明时使用open关键字和override关键字开启重写

1
2
3
4
5
6
7
8
9
10
open class Person(open var name: String) {
    open fun say() {
        // ...
    }
}
class Student(override var name: String) : Person(name) {
    override fun say() {
        // ...
    }
}

super泛型:用于选择父类或接口的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
open class A {
    open fun f () { print("A") }
    fun a() { print("a") }
}

interface B {
    fun f() { print("B") } //接口的成员变量默认是 open 的
    fun b() { print("b") }
}

class C : A() , B{
    override fun f() {
        super<A>.f()//调用 A.f()
        super<B>.f()//调用 B.f(),接口默认实现
    }
}

抽象类

kotlin的抽象类与java类似,使用abstract关键字,无法实例化,构造器只能由子类调用

1
2
3
4
5
6
7
8
abstract class Person {
    abstract var age: Int
    abstract fun say()
}
class Student : Person() {
    override var age: Int = 0
    override fun say()
}

接口

kotlin接口与java接口类似,使用interface关键字

  • 接口可以包含属性,属性是抽象的,实现类需要重写属性或在接口中为val属性提供getter
  • 接口可以提供默认实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
interface Listener {
    val attr: String
    var attr1: String
    // getter只能用于val属性
    val attr2: String
    	get() = "value"
    
    fun method() {
        // 接口默认实现
    }
}
class Person : Listener {
    override val attr: String = ""
    override var attr1: String = ""
}

嵌套类与内部类

嵌套类与内部类都是定义在一个类中的类,区别在于是否持有外部引用,嵌套类不持有外部引用,内部类持有外部引用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Outer {
    private val bar: Int = 1
    // 嵌套类
    class Nested {
        fun foo() = 2
    }
}
class Outer {
    private val bar: Int = 1
    var v = "成员属性"
    // 内部类
    inner class Inner {
        fun foo() = bar  // 访问外部类成员
        fun innerTest() {
            var o = this@Outer //获取外部类的成员变量
            println("内部类可以引用外部类的成员,例如:" + o.v)
        }
    }
}

数据类

声明

使用data关键字声明,编译器会自动生成equals、hashCode、toString、ComponentN和copy方法

  • 数据类的主构造器必须包含至少一个参数
  • 属性不能是open、abstract、sealed、inner

编译器只会对主构造器中声明的属性生成方法,对在类体中声明的属性不会提供方法

kotlin提供了Pair和Triple数据类

1
data class Person(name: String, age: Int)

解构声明

能将一个对象中的属性同时解构为多个变量,该对象的类需要实现componentN操作符,编译器会将解构声明编译为componentN函数的调用

解构声明中的参数可以声明类型,支持类型推断,解构声明如果存在不需要的变量,可以使用_替换

1
2
3
4
5
6
7
8
9
10
11
12
13
class Person(var name: String, var age: Int) {
    operator fun component1(): String = name
    operator fun component2(): Int = age
}

fun main() {
    val p = Person("hello", 20)
    val (name, age) = person
    // 编译器根据解构参数列表调用调用对应位置的componentN函数
    // 编译器将解构声明编译为以下代码
    val name = person.component1()
    val age = person.component2()
}

返回值解构

可以在返回一个对象的地方使用解构声明替换

1
2
3
4
5
6
7
8
9
for ((key, value) in map) {
    // 对每个entry使用解构声明
}
// 数据类会自动生成componentN函数,可以用于返回值解构
data class Result(val result: Int, val status: Status)
fun function(): Result {
    return Result(result, status)
}
val (result, status) = function()

Lambda参数解构

在lambda表达式的参数部分也可以使用解构声明,一个解构声明看做一个参数,类型声明与推断与一般参数相同

1
2
map.mapValues { entry -> "${entry.value}!" }
map.mapValues { (key, value) -> "$value!" }

密封类

使用sealed关键字声明,与枚举类类似,密封类的子类必须与密封类位于同一文件或者嵌套在密封类中

密封类的子类可以拥有自己的属性和方法,可以看做是限制子类类型的抽象类或者扩展的枚举类,通常用于when匹配

Kotlin枚举类:与java枚举类相同,使用enum关键字声明

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
sealed class Obj
class SubClass1 : Obj() {
    var attr: Int = 0
}
class SubClass2 : Obj() {
    var attr: Int = 0
}
// 嵌套在密封类中
sealed class Obj {
    class SubClass1 : Obj() {
        var attr: Int = 0
    }
    class SubClass2 : Obj() {
        var attr: Int = 0
    }
}

类型别名

使用typealias关键字可以为类型、函数创建别名

1
2
typealias NodeSet = Set<Network.Node>
typealias FileTable<K> = MutableMap<K, MutableList<File>>

函数别名

1
2
typealias MyHandler = (Int, String, Any) -> Unit
typealias Predicate<T> = (T) -> Boolean

内部类和嵌套类别名

1
2
3
4
5
6
7
8
9
class A {
    inner class Inner
}
class B {
    class Nest
}

typealias AInner = A.Inner
typealias BNest = B.Nest

对象表达式

对象表达式用于创建一个匿名对象,使用object关键字,对象表达式中可以访问包含它的作用域中的变量

若没有指定继承或实现,匿名对象继承Any类

1
2
3
object : <superClass>|<implementation> {
    // 对象体
}

对象声明

使用object的对象声明可以实现单例模式,天然实现了线程安全和延迟初始化(懒汉式)

对象声明也可以继承和实现接口

1
2
3
4
5
object Singleton {
    // 对象体
}
var s = Singleton  // 赋值引用
Singleton.method()  // 直接引用

伴生对象

表示一个伴随着类的对象,在类加载时进行初始化,使用companion关键字声明

每个类只能有一个伴生对象,伴生对象也可以继承或实现接口,他们依然是实例成员

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Person {
    // 匿名伴生对象
    companion object {
        // 对象体
        fun method() {
            // ...
        }
    }
}
class Student {
    // 具名伴生对象
    companion object Obj {
        // 对象体
        fun method() {
            // ...
        }
    }
}
// 引用伴生对象
Person.Companion.method()
Student.Obj.method()
// 省略伴生对象引用
Person.method()
Student.method()
// 获取伴生对象,通过类名引用
var p = Person
var s = Student

扩展

能在外部对一个类或接口扩展新的方法和属性,不需要继承或装饰器

扩展函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Person
fun Person.say() {
    // 扩展一个say方法
    // 可以使用this,this指向Person类,this称为接收者
}
class Student<T>
fun <T> Student<T>.study() {
    // 扩展一个泛型方法
}
// 可空接收者
fun Person?.say() {
    // 可以为一个可空接收者扩展函数
    // 此时this也是可空的,需要进行空判断
}
var p: Person = Person()
var pn: Person? = Person()
// 非空对象可以安全调用非空和可空的扩展函数
p.say()
p.speak()
// 可空对象需要使用可空调用调用非空的扩展函数
pn?.say()
pn.speak()

扩展函数静态解析:扩展函数的调用是静态解析的,根据代码的编写静态调用,与运行时无关,若通过一个父类引用调用子类的扩展函数,此时无法调用子类扩展函数,只能调用到父类扩展函数

扩展属性

扩展属性不能拥有幕后字段field,因此无法直接赋值初始化

1
2
3
4
5
6
7
var Person.name: String
    get() = ""
    set(value) {
        // ...
    }
val Person.age: Int
    get() = 0

伴生对象扩展

类的伴生对象也支持扩展函数和扩展属性

1
2
3
4
5
6
7
8
9
class Person {
    companion object {}
}
fun Person.Companion.method() {
    // ...
}
var Person.Companion.attr: Int
    get() = 0
    set(value) {}

类声明扩展

在一个类A中声明另一个类B,可以在类A中对类B进行扩展,类A称为分发接收者,类B称为扩展接收者

1
2
3
4
5
6
7
8
9
10
11
12
13
class B
class A {
    var b: B = B()

    // 扩展类B的属性和方法只能在类A中使用
    var B.attr: Int
        get() = 0
        set(value) {}

    fun B.say() {
        // ...
    }
}

分发接收者虚拟解析:分发接收者的扩展可设为open,由子类重写,子类对象调用扩展时,会使用子类重写的扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
open class Base { }

class Derived : Base() { }

open class BaseCaller {
    open fun Base.printFunctionInfo() {
        println("Base extension function in BaseCaller")
    }

    // 静态解析不会调用
    open fun Derived.printFunctionInfo() {
        println("Derived extension function in BaseCaller")
    }

    fun call(b: Base) {
        b.printFunctionInfo()   // 调用扩展函数
    }
}

class DerivedCaller: BaseCaller() {
    override fun Base.printFunctionInfo() {
        println("Base extension function in DerivedCaller")
    }

    // 静态解析不会调用
    override fun Derived.printFunctionInfo() {
        println("Derived extension function in DerivedCaller")
    }
}
fun main() {
    // call方法中使用的是Base类的扩展,静态解析后只会使用Base类的扩展
    BaseCaller().call(Base())   // “Base extension function in BaseCaller”
    // 子类重写了扩展,调用扩展时,使用的是子类重写的扩展
    DerivedCaller().call(Base())  // “Base extension function in DerivedCaller”
    DerivedCaller().call(Derived())  // “Base extension function in DerivedCaller”
}
本文由作者按照 CC BY 4.0 进行授权