【笔记】Kotlin的面向对象

前言

Kotlin的面向对象学习笔记

定义类

1
2
3
class 类名 {
...
}

类中定义主构造函数

  • 通过constructor作为函数名的函数是构造函数
  • 主构造函数直接定义在类名后

constructor:主构造函数名

1
2
3
class 类名 constructor(形参名: 数据类型) {
...
}
  • 如果主构造函数没有权限修饰符,则可以省略主构造函数的函数名
1
2
3
class 类名(形参名: 数据类型) {
...
}

类中定义次构造函数

  • 在类内定义的构造函数为次构造函数

  • 如果显式定义了主构造函数,在次构造函数中必须通过this()直接或间接的委托给主构造函数

1
2
3
4
5
class 类名 constructor(var 形参名: 数据类型) {
constructor(形参名: 数据类型) {
this(形参名)
}
}
  • 如果没有显式定义主构造函数,则次构造函数中无需显式的委托给主构造函数,其内部已经隐式的委托给了主构造函数
1
2
3
4
5
class 类名 {
constructor(形参名: 数据类型) {
...
}
}

次构造函数的重载

  • 次构造函数可以通过重载定义多个
1
2
3
4
5
6
7
8
class 类名 {
constructor(形参名: 数据类型) {
...
}
constructor(形参名1: 数据类型, 形参名2: 数据类型) {
...
}
}

类中定义实例属性

  • 定义实例属性时必须赋初值
1
2
3
class 类名 {
var 实例属性名: 数据类型 = 值
}

实例属性延迟初始化

  • 通过lateinit关键字修饰的属性是延迟初始化的属性,可以在声明时不赋初值
  • 延迟初始化的属性是懒加载的,必须在使用之前进行赋值
  • 只有引用类型才可以延迟初始化,基本类型不能延迟初始化
1
2
3
class 类名 {
lateinit var 延迟初始化属性名: 数据类型
}

实例属性的getter和setter

  • 没有显式定义getter和setter时,实例属性也有内置的getter和setter
1
2
3
4
5
class 类名 {
var 实例属性名: 数据类型 = 值
get() = field
set(value) { field = value }
}
1
2
3
4
class 类名 {
val 实例属性名: 数据类型 = 值
get() = field
}

主构造函数通过形参定义实例属性

  • 主构造函数的形参可以被varval修饰,当主构造函数的形参被varval修饰时,则表示同时声明了主构造函数的形参名和实例属性名
1
2
3
class 类名 constructor(var 实例属性名: 数据类型) {
...
}
  • 次构造函数的形参不允许被varval修饰

类中定义实例方法

1
2
3
4
5
class 类名 {
fun 实例方法名() {
...
}
}

类中定义初始化代码块

  • 通过init定义初始化代码块
  • 初始化代码会在构造函数执行前执行
1
2
3
4
5
class 类名 {
init {
...
}
}
  • 初始化函数可以有多个,会按照顺序执行
1
2
3
4
5
6
7
8
class 类名 {
init {
...
}
init {
...
}
}

创建对象

  • 通过构造函数创建对象,主构造函数和次构造函数都可以用于创建对象
1
var 对象名: 类名 = 类名()
  • 定义对象时可以自动推导数据类型
1
var 对象名 = 类名()
  • 调用构造函数创建对象,为构造函数传递实参
1
var 对象名: 类名 = 类名(实参)

对象访问实例属性

1
println(对象名.实例属性名)
1
对象名.实例属性名 = 值

对象访问实例方法

1
对象名.实例方法名()

方法通过this关键字访问当前实例

访问当前实例属性

1
2
3
4
5
6
7
class 类名 {
var 实例属性名: 数据类型 = 值
fun 实例方法名() {
println(this.实例属性名)
this.实例属性名 = 值
}
}

访问当前实例方法

1
2
3
4
5
6
7
8
9
10
11
12
class 类名 {
fun 实例方法名1() {
...
}
fun 实例方法名2(): 返回值类型 {
...
}
fun 实例方法名3() {
this.实例方法名1()
var 变量名 = this.实例方法名2()
}
}

方法的重载

  • 同名方法可以重载
1
2
3
4
5
6
7
8
class 类名 {
fun 实例方法名() {
...
}
fun 实例方法名(形参名: 数据类型) {
...
}
}

对象空值

定义可空类型变量

1
var 对象名: 类名? = null

通过判空使用可空类型变量

  • 手动检测对象不为空值时才调用对象的属性或方法
1
2
3
if (对象名 != null) {
println(对象名.属性名)
}

强制使用可空类型变量

  • 使用!!空值断言运算符调用对象的属性或方法
1
println(对象名!!.属性名)

自动在不为空值时才使用可空类型变量

  • 如果对象为空值,且调用了对象的属性或方法,不会报错而是返回空值
1
println(对象名?.属性名)

当可空类型变量为空值时赋予默认值

  • 通过Elvis运算符?:为空值变量赋予默认值
1
var 变量名 = 对象名 ?: 默认值

解构对象中的属性

  • 类中通过定义component()方法,返回可以被解构的值
  • 通过在component加序号的形式定义结构的顺序
  • 通过_来跳过对应属性的结构
1
2
3
4
5
6
7
8
9
10
11
class 类名(属性名1: 数据类型, 属性名2: 数据类型) {
operator fun component1() = 属性名1
operator fun component2(): 数据类型 {
return 属性名2
}
}

fun main() {
var (变量名1, 变量名2) = 类名(属性名1, 属性名2)
var (_, 变量名3) = 类名(属性名1, 属性名2)
}

权限修饰符

public、无修饰符:当前项目
internal:当钱包
protected:当前类及子类
private:当前类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class 类名 {
fun method1() {
...
}
public fun method2() {
...
}
internal fun method3() {
...
}
protected fun method4() {
...
}
private fun method5() {
...
}
}

继承

  • 通过open关键字允许当前类被继承
  • 通过在类名后添加:父类构造函数继承父类,在调用子类构造函数时,父类的构造函数也会被执行
1
2
3
4
5
open class GrandFather {}

open class Father: GrandFather() {}

class Son : Father() {}
  • 通过this()调用自己的主构造函数,通过super()调用父类构造函数,次构造函数不能饶过主构造函数调用父类构造函数,必须直接或间接的调用主构造函数
1
2
3
4
5
open class Father {}

class Son() : Father() {
constructor(形参名: 数据类型) : this()
}

公共父类

  • 所有类的父类是Any
1
class Son : Any() {}
  • Any类中包含equals()hashCode()toString()方法
    • equals()方法与==运算符完全等价,子类重写equals()方法时,必须重写父类equals()方法

调用父类属性

1
2
3
4
5
6
7
8
9
open class Father {
open var 属性名: 数据类型 = 值
}

class Son : Father() {
方法名() {
println(super.属性名)
}
}

调用父类方法

1
2
3
4
5
6
7
8
9
10
11
open class Father {
open 方法名() {
...
}
}

class Son : Father() {
方法名2() {
super.方法名()
}
}

重写父类属性

  • 子类通过override关键字定义被重写的属性
1
2
3
4
5
6
7
open class Father {
open var 属性名: 数据类型 = 值
}

class Son(override var 属性名: 数据类型) : Father() {
...
}
1
2
3
4
5
6
7
open class Father {
open var 属性名: 数据类型 = 值
}

class Son : Father() {
override var 属性名: 数据类型 = 值
}

重写父类方法

  • 子类通过override关键字定义被重写的方法
1
2
3
4
5
6
7
8
9
10
11
open class Father {
方法名() {
...
}
}

class Son : Father() {
override 方法名() {
...
}
}

多态

  • 父类引用指向子类实现
1
2
3
4
5
6
7
open class Father {}

class Son : Father() {}

fun main() {
var 对象名: Father = Son()
}

判断对象属于指定类

  • 不论是属于其类还是其父类,都会返回true
1
对象名 is 类名

判断对象不属于指定类

1
对象名 !is 类名

强制类型转换

1
对象名 as 类名

强制转换可空类型

1
对象名 as? 类名

抽象类

  • 通过abstract关键字定义的类是抽象类,抽象类中可以通过abstract关键字定义抽象属性和抽象方法
1
2
3
4
abstract class 抽象类名 {
abstract var 抽象属性名: 数据类型
abstract fun 抽象方法名()
}
1
2
3
4
5
6
class 类名 : 抽象类名() {
override var 抽象属性名: 数据类型 = 值
override fun 抽象方法名() {
...
}
}

接口

  • 通过interface关键字定义的类是接口,接口中可以定义抽象属性和抽象方法,接口中的抽象属性和抽象方法无需使用abstract关键字定义
  • 接口中可以为抽象属性定义Getter方法
  • 接口中可以为抽象方法定义默认实现
1
2
3
4
5
6
7
interface class 抽象类名 {
var 抽象属性名: 数据类型
get() = 值
fun 抽象方法名() {
...
}
}
  • 子类可以实现多个接口
1
2
3
4
5
6
class 类名 : 接口1, 接口2 {
override var 抽象属性名: 数据类型 = 值
override fun 抽象方法名() {
...
}
}

接口冲突

  • 使用指定的接口
1
2
3
4
5
6
7
8
9
10
11
interface class 接口名1 {
fun method() {}
}
interface class 接口名2 {
fun method() {}
}
class 类名 : 接口名1, 接口名2 {
override fun method() {
super<接口名1>.method()
}
}

方法和属性的扩展

为指定类扩展实例方法

  • 扩展方法的this指向调用的对象,this可以省略
1
2
3
4
5
6
7
8
9
10
class 类名 {}

fun 类名.扩展方法名() {
...
}

fun main() {
var 对象名 = 类名()
对象名.扩展方法名()
}

通过Lambda表达式定义扩展方法

1
2
3
4
5
6
7
8
9
10
11
class 类名 {}

fun main() {

var fn: 类名.扩展方法名(形参数据类型) -> 返回值类型 = {
...
}

var 对象名 = 类名()
fn(对象名, 实参)
}

为指定类扩展实例实例

1
2
3
4
5
6
7
8
9
class 类名 {}

fun 类名.扩展属性名: 数据类型
get() = 值

fun main() {
var 对象名 = 类名()
println(对象名.扩展属性名)
}

扩展方法冲突

1
2
3
4
5
6
7
8
9
10
11
12
13
class 类名 {
fun 扩展方法名() {
...
}
fun 方法名 {
this.扩展方法名() // 调用扩展方法
this@类名.扩展方法名() // 调用类中定义的同名方法
}
}

fun 类名.扩展方法名() {
...
}

匿名类

通过匿名类创建对象

  • 通过对象表达式创建对象
1
2
3
4
5
var 对象名 = object {
var 属性名: 数据类型 = 值

fun 方法名() {}
}

通过匿名子类创建对象

1
2
3
4
5
var 对象名 = object : 父类 {
var 属性名: 数据类型 = 值

fun 方法名() {}
}

函数式接口

  • 只包含一个抽象方法(单一抽象方法)的接口

函数式接口的定义

1
2
3
fun interface 函数式接口名 {
fun 单一抽象方法名()
}

函数式接口的使用

1
2
3
4
5
var 方法名 = 函数式接口名 {
单一抽象方法实现
}

方法名()

单例类

  • 只能创建出一个实例的类

定义单例类

1
2
3
4
5
object 类名 {
var 属性名: 数据类型 = 值

fun 方法名() {}
}

通过单例类创建单实例

1
var 对象名 = 类名

伴生对象

  • 伴生对象中定义的属性和方法可以通过类名直接访问
  • 伴生对象是单例的

定义伴生对象

  • 通过companion object关键字在类中定义伴生对象
1
2
3
4
5
6
7
class 类名 {
companion object {
var 属性名: 数据类型 = 值

fun 方法名() {}
}
}

通过类名访问属性和方法

1
2
3
类名.属性名 = 值

类名.方法名()

委托模式

委托抽象方法的实现

  • 将接口中所有待实现的抽象方法交给指定属性
1
2
3
4
5
6
7
interface 接口名 {
fun 抽象方法名()
}

class 子类名(var 属性名: 接口名) : 接口名 by 属性名 {
属性名.抽象方法名()
}
  • 上述接口就是委托类,子类就是被委托类
1
var 变量名: 被委托类名 by 委托类名()

通过委托模式实现观察者

1
2
3
4
5
class 类名 {
var 属性名: 数据类型 by Delegates.observe(初始值) { prop, old, new
println("$prop 的值发生了变化,从$old 变为 $new")
}
}

委托属性

1
2
3
class 类名(var 属性名1: 数据类型) {
var 属性名2: 数据类型 by ::属性名1
}

通过Map为多属性赋值

1
2
3
4
5
6
7
8
9
class 类名(var map: Map<String, Any>) {
var 属性名1: 数据类型 by map
var 属性名2: 数据类型 by map
}

var 对象名 = 类名(mapOf(
属性名1 to 值,
属性名2 to 值,
))

密封类

定义密封类

  • 通过sealed关键字定义的类是密封类
1
sealed class 类名

Unit类型

  • 相当于Java中的void
  • Kotlin中的Unit是一个单例对象
1
var 变量名: Unit = Unit

完成

参考文献

哔哩哔哩——青空の霞光