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