(二)Kotlin—类与对象

文章目录

  • (二)Kotlin------类与对象
    • 1.函数
      • [1.1 使用函数](#1.1 使用函数)
      • [1.2 变量](#1.2 变量)
      • [1.3 实用库函数](#1.3 实用库函数)
      • [1.4 高阶函数](#1.4 高阶函数)
      • [1.5 lambda 表达式](#1.5 lambda 表达式)
      • [1.6 内联函数](#1.6 内联函数)
    • 2.类与对象
      • [2.1 类的定义与对象创建](#2.1 类的定义与对象创建)
      • [2.2 对象的初始化](#2.2 对象的初始化)
      • [2.3 类的成员函数](#2.3 类的成员函数)
      • [2.4 运算符重载函数](#2.4 运算符重载函数)
      • [2.5 中缀函数](#2.5 中缀函数)
      • [2.6 空值和空类型](#2.6 空值和空类型)
      • [2.7 解构声明](#2.7 解构声明)
      • [2.8 包和导入](#2.8 包和导入)
      • [2.9 访问权限控制](#2.9 访问权限控制)
    • 3.封装、继承和多态
      • [3.1 封装](#3.1 封装)
      • [3.2 继承](#3.2 继承)
      • [3.3 属性覆盖](#3.3 属性覆盖)
      • [3.4 多态](#3.4 多态)
      • [3.5 顶层 Any 类](#3.5 顶层 Any 类)
      • [3.6 抽象类](#3.6 抽象类)
      • [3.7 接口](#3.7 接口)
      • [3.8 类的扩展](#3.8 类的扩展)

(二)Kotlin------类与对象

1.函数

1.1 使用函数

Kotlin 使用 fun 定义函数,参数写法是 参数名: 类型,返回值写在参数列表后面。

kotlin 复制代码
fun 函数名称(参数名: 参数类型): 返回值类型 {
    // 函数体
}

没有返回值时,返回类型是 Unit,类似 Java 的 void,通常可以省略。

kotlin 复制代码
fun hello() {
    println("Hello Kotlin")  // Hello Kotlin
}

有返回值时,使用 return 返回结果。

kotlin 复制代码
fun sum(a: Int, b: Int): Int {
    return a + b
}

fun main() {
    println(sum(3, 5))  // 8
}

如果函数体只有一行,可以写成表达式函数。

kotlin 复制代码
fun sum(a: Int, b: Int): Int = a + b

fun main() {
    println(sum(10, 20))  // 30
}

函数参数默认是只读的,不能在函数内部重新赋值。如果需要修改,应定义新的局部变量。

kotlin 复制代码
fun addOne(x: Int): Int {
    var num = x
    num++
    return num
}

fun main() {
    println(addOne(9))  // 10
}

函数内部可以继续定义函数,内部函数只能在外部函数内部使用,并且可以访问外部函数的局部变量。

kotlin 复制代码
fun outer() {
    val a = 10

    fun inner() {
        println(a)  // 10
    }

    inner()
}

fun main() {
    outer()
}

1.2 变量

声明一个变量的完整语法如下:

kotlin 复制代码
var <propertyName>[: <PropertyType>] [= <property_initializer>]
    [<getter>]
    [<setter>]
  • getter:用于获取这个变量的值,默认情况下直接返回当前这个变量的值
  • setter:用于修改这个变量的值,默认情况下直接对这个变量的值进行修改

Kotlin 中的属性本质上带有 getter 和 setter。var 可读可写,val 只读。

kotlin 复制代码
var name: String = "小明"
val age: Int = 18

完整属性语法如下,一般不需要写这么完整,了解即可。

kotlin 复制代码
var propertyName: Type = 初始值
    get() = field
    set(value) {
        field = value
    }

field 是 Kotlin 的后备字段,用在自定义 getter/setter 中表示当前属性真正存储的值。

kotlin 复制代码
var text: String = "Hi"
    get() = field + " Kotlin"

fun main() {
    println(text)  // Hi Kotlin
}

自定义 setter 可以在赋值时增加额外逻辑。

kotlin 复制代码
var name: String = "小明"
    set(value) {
        println("正在修改name")  // 正在修改name
        field = value
    }

fun main() {
    name = "小红"
    println(name)  // 小红
}

注意:val 没有 setter,因为它不能被重新赋值。

1.3 实用库函数

println 是最常用的输出函数。

kotlin 复制代码
fun main() {
    println("Hello World")  // Hello World
}

readln() 用于读取控制台输入,返回 String

kotlin 复制代码
fun main() {
    val text = readln()
    println("读取到用户输入:$text")  // 假设输入Kotlin,则输出:读取到用户输入:Kotlin
}

数学函数需要导入 kotlin.math.*

kotlin 复制代码
import kotlin.math.*

fun main() {
    println(2.0.pow(3.0))  // 8.0
    println(abs(-10))      // 10
    println(max(19, 20))   // 20
    println(min(2, 4))     // 2
    println(sqrt(9.0))     // 3.0
}

三角函数也在 kotlin.math 中。

kotlin 复制代码
import kotlin.math.*

fun main() {
    println(sin(PI / 2))  // 1.0
    println(cos(PI))      // -1.0
    println(tan(PI / 4))  // 0.9999999999999999
}

对数和取整函数示例:

kotlin 复制代码
import kotlin.math.*

fun main() {
    println(ln(E))          // 1.0
    println(log10(100.0))   // 2.0
    println(log2(8.0))      // 3.0
    println(ceil(4.5))      // 5.0
    println(floor(5.6))     // 5.0
}

1.4 高阶函数

函数也可以作为变量保存。函数类型 格式为:(参数类型列表) -> 返回值类型。注意,即使是 Unit 返回类型也不能省略。

kotlin 复制代码
var func0: (Int) -> Unit
var func1: (Double, Double) -> String  // 代表两个Double参数返回String类型的函数

高阶函数指参数或返回值是函数类型的函数。

kotlin 复制代码
fun test(other: (Int) -> String) {
    println(other(1))  // 数字是1
}

fun main() {
    test { num -> "数字是$num" }
}

可以使用 ::函数名 引用已有函数。

kotlin 复制代码
fun toLength(str: String): Int {
    return str.length
}

fun main() {
    val func: (String) -> Int = ::toLength
    println(func("Kotlin"))  // 6
}

也可以使用匿名函数。

kotlin 复制代码
fun main() {
    val func: (String) -> Int = fun(str: String): Int {  //  这里写了fun关键字后,并没有编写函数名称,这种函数就是匿名函数,因为在这里也不需要什么名字,只需要参数列表函数体
        println("内容是$str")  // 内容是Hello
        return str.length
    }

    println(func("Hello"))  // 5
}

1.5 lambda 表达式

Lambda 是匿名函数的简写形式。只有一个参数时,可以用 it 表示该参数。

kotlin 复制代码
fun main() {
    val func: (String) -> Int = {  // 一个Lambda表达式只需要直接在花括号中编写函数体即可
        println("参数是$it")  // 参数是HelloWorld
        666
    }

    println(func("HelloWorld"))  // 666
}

多个参数时,需要显式写出参数名。

kotlin 复制代码
fun main() {
    val func: (String, String) -> Unit = { a, b ->
        println("第一个参数$a,第二个参数$b")  // 第一个参数Hello,第二个参数World
    }

    func("Hello", "World")
}

不使用的参数可以用 _ 代替。

kotlin 复制代码
fun main() {
    val func: (String, String) -> Unit = { _, b ->
        println("第二个参数$b")  // 第二个参数World
    }

    func("Hello", "World")
}

如果函数的最后一个参数是函数类型,可以使用 尾随 lambda,把 lambda 写到括号外。

kotlin 复制代码
fun test(i: Int, func: (Int) -> String) {
    println(func(i))  // 收到的参数为1
}

fun main() {
    test(1) { "收到的参数为$it" }
}

Lambda 中没有办法直接使用 return 语句返回结果,Lambda 中需要提前返回时,可以使用标签返回。

kotlin 复制代码
fun test(func: (Int) -> String) {
    println(func(66))  // 我是提前返回的结果
}

fun main() {
    val func: (Int) -> String = test@{
        if (it > 10) return@test "我是提前返回的结果"
        "收到的参数为$it"
    }

    test(func)
}

尾随 lambda 默认可以使用函数名作为标签。

kotlin 复制代码
fun testName(func: (Int) -> String) {
    println(func(66))  // 我是提前返回的结果
}

fun main() {
    testName {
        if (it > 10) return@testName "我是提前返回的结果"
        "收到的参数为$it"
    }
}

1.6 内联函数

高阶函数会传入 lambda,可能产生额外对象和调用开销。inline 可以让编译器把函数体和 lambda 代码直接展开到调用处,减少开销。

kotlin 复制代码
inline fun test(func: (String) -> Unit) {
    println("这是一个内联函数")  // 这是一个内联函数
    func("HelloWorld")
}

fun main() {
    test { println("打印:$it") }  // 打印:HelloWorld
}

可以简单理解为,编译后大致等价于:

kotlin 复制代码
fun main() {
    println("这是一个内联函数")  // 这是一个内联函数
    val it = "HelloWorld"
    println("打印:$it")        // 打印:HelloWorld
}

注意:内联函数中的函数参数主要用于直接调用,不适合再保存到变量中使用。

2.类与对象

2.1 类的定义与对象创建

Kotlin 使用 class 定义类。空类可以省略花括号。

kotlin 复制代码
class Student

主构造函数 写在类名后面。没有注解或可见性修饰符时,constructor 可以省略。

kotlin 复制代码
class Student constructor(name: String, age: Int)
class Student2(name: String, age: Int)

如果希望构造参数同时成为属性,需要加 varval

kotlin 复制代码
class Student(var name: String, val age: Int)

fun main() {
    val stu = Student("小明", 18)
    println(stu.name)  // 小明
    println(stu.age)   // 18
}

也可以在类体中声明属性,但必须初始化,或者使用 lateinit 延迟初始化。

kotlin 复制代码
class Student {
    lateinit var name: String
    var age: Int = 0
}

fun main() {
    val stu = Student()
    stu.name = "小明"
    stu.age = 18
    println(stu.name)  // 小明
    println(stu.age)   // 18
}

Kotlin 创建对象不需要 new

kotlin 复制代码
class Student(var name: String, val age: Int)

fun main() {
    val stu = Student("小明", 18)
    println("对象的name = ${stu.name}, age = ${stu.age}")  // 对象的name = 小明, age = 18
}

次要构造函数使用 constructor,如果类有主构造函数,次要构造函数必须直接或间接调用主构造函数。

kotlin 复制代码
class Student(var name: String, val age: Int) {
    // 次要构造函数中的参数仅仅是表示传入的参数,不能像主构造函数那样定义属性
    // 这里的this表示是当前这个类,this() 就是调用当前类的构造函数
    constructor(name: String) : this(name, 18)
}

fun main() {
    val stu = Student("小明")
    println(stu.age)  // 18
}

如果没有主构造函数,次要构造函数可以直接写在类体中。

kotlin 复制代码
class Student {
    var name: String = ""

    constructor(name: String) {
        this.name = name
        println("我的名字是: $name")  // 我的名字是: 小明
    }
}

fun main() {
    Student("小明")
}

主构造函数与次要构造函数区别:

  • 主构造函数: 可以直接在主构造函数中定义类属性,使用更方便,但是主构造函数只能存在一个,并且无法编写函数体,只有为类属性做初始化赋值的效果。
  • 辅助(次要)构造函数: 可以存在多个,并且可以自定义函数体,但是无法像主构造函数那样定义类属性,并且当类具有主构造函数时,所有次要构造函数必须直接或间接地调用主构造函数。

2.2 对象的初始化

主构造函数不能写函数体,初始化逻辑写在 init 代码块中。

kotlin 复制代码
class Student(var name: String, var age: Int) {
    init {
        println("我是初始化操作")  // 我是初始化操作
        if (age < 18) age = 18
        println("初始化操作结束")  // 初始化操作结束
    }
}

fun main() {
    val stu = Student("小明", 16)
    println(stu.age)  // 18
}

多个 init 会按照代码顺序执行。

kotlin 复制代码
class Student {
    init {
        println("一号初始化操作")  // 一号初始化操作
    }

    init {
        println("二号初始化操作")  // 二号初始化操作
    }
}

fun main() {
    Student()
}

如果通过次要构造函数创建对象,执行顺序是:主构造函数相关初始化和 init 先执行,次要构造函数体后执行

kotlin 复制代码
class Student(var name: String, var age: Int) {
    init {
        println("我是初始化代码块")  // 我是初始化代码块
    }

    constructor(name: String) : this(name, 18) {
        println("我是次要构造函数")  // 我是次要构造函数
    }
}

fun main() {
    Student("小明")
}

2.3 类的成员函数

类中的函数叫成员函数,通过对象调用。

kotlin 复制代码
class Student(var name: String, var age: Int) {
    fun hello() {
        println("大家好啊")  // 大家好啊
    }
}

fun main() {
    val stu = Student("小明", 18)
    stu.hello()
}

当成员属性和函数参数同名时,使用 this 表示当前对象。

kotlin 复制代码
class Student(var name: String, var age: Int) {
    fun hello(name: String) {
        println("参数name=$name")        // 参数name=小红
        println("成员name=${this.name}") // 成员name=小明
    }
}

fun main() {
    val stu = Student("小明", 18)
    stu.hello("小红")
}

2.4 运算符重载函数

Kotlin 可以通过固定名称的函数重载运算符,例如 + 对应 plus。需要添加 operator 关键字。

kotlin 复制代码
class Student(var name: String, var age: Int) {
    operator fun plus(another: Student): Student {
        return Student(this.name + another.name, this.age + another.age)
    }
}

fun main() {
    val a = Student("小米", 18)
    val b = Student("华为", 19)
    val c = a + b
    println("名称:${c.name},年龄:${c.age}")  // 名称:小米华为,年龄:37
}

常见一元运算符:

符号 对应函数
+a a.unaryPlus()
-a a.unaryMinus()
!a a.not()
a++ a.inc()
a-- a.dec()

常见二元运算符:

符号 对应函数
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..<b a.rangeUntil(b)

in!in 对应 contains

符号 对应函数
a in b b.contains(a)
a !in b !b.contains(a)

复合赋值运算符:

符号 对应函数
a += b a.plusAssign(b)
a -= b a.minusAssign(b)
a *= b a.timesAssign(b)
a /= b a.divAssign(b)
a %= b a.remAssign(b)

比较运算符对应 compareTo

运算符 对应函数
a > b a.compareTo(b) > 0
a < b a.compareTo(b) < 0
a >= b a.compareTo(b) >= 0
a <= b a.compareTo(b) <= 0

示例:

kotlin 复制代码
class Student(var name: String, var age: Int) {
    operator fun compareTo(other: Student): Int {
        return this.age - other.age
    }
}

fun main() {
    val a = Student("小明", 18)
    val b = Student("小红", 20)
    println(a > b)   // false
    println(a < b)   // true
}

2.5 中缀函数

infix 中缀函数可以省略点和括号调用。要求:必须是成员函数或扩展函数,只能有一个参数,参数不能有默认值。

kotlin 复制代码
class Student(var name: String, var age: Int) {
    infix fun test(string: String): String {
        return name + string
    }
}

fun main() {
    val student = Student("小明", 18)
    println(student test "我爱你")  // 小明我爱你
    println(student.test("我爱你")) // 原始写法
}

2.6 空值和空类型

Kotlin 默认不允许变量为 null

kotlin 复制代码
var str: String = "Hello"
// str = null  // 编译错误

如果允许为空,需要 在类型后加 ?

kotlin 复制代码
fun main() {
    var str: String? = null
    println(str)  // null
}

安全调用 ?.:对象为 null 时,不继续访问成员,直接返回 null

kotlin 复制代码
fun main() {
    var str: String? = null
    println(str?.length)  // null

    str = "Kotlin"
    println(str?.length)  // 6
}

Elvis 运算符 ?::左边为 null 时返回右边的默认值。

kotlin 复制代码
fun main() {
    val str: String? = null
    val len: Int = str?.length ?: 0
    println(len)  // 0
}

非空断言 !! 会强制把可空类型当作非空类型使用。如果实际为 null,会抛出空指针异常,尽量少用。

kotlin 复制代码
fun main() {
    val str: String? = "Kotlin"
    println(str!!.length)  // 6
}

2.7 解构声明

解构可以把对象拆成多个变量。自定义类需要提供 componentN 函数。

kotlin 复制代码
class Student(var name: String, var age: Int) {
    operator fun component1() = name   //使用component1表示解构出来的第一个参数
    operator fun component2() = age    //使用component2表示解构出来的第二个参数
  	operator fun component3...  //以此类推
}

fun main() {
    val student = Student("小明", 18)
    val (name, age) = student
    println(name)  // 小明
    println(age)   // 18
}

解构也可以用在 lambda 参数中。

kotlin 复制代码
class Student(var name: String, var age: Int) {
    operator fun component1() = name
    operator fun component2() = age
}

fun main() {
    val func: (Student) -> Unit = { (name, age) ->
        println("名字: $name, 年龄: $age")  // 名字: 小明, 年龄: 18
    }

    func(Student("小明", 18))
}

不需要的解构变量可以用 _ 忽略。

kotlin 复制代码
fun main() {
    val student = Student("小明", 18)
    val (_, age) = student
    println(age)  // 18
}

2.8 包和导入

Kotlin 使用 package 声明包,使用 import 导入其他包中的内容。

kotlin 复制代码
package com.example.demo

import kotlin.math.max

fun main() {
    println(max(10, 20))  // 20
}

如果名称冲突,可以使用 as 起别名。

kotlin 复制代码
import com.test.message as outer

2.9 访问权限控制

Kotlin 的可见性修饰符有四个:publicprivateprotectedinternal。默认是 public

顶层声明

修饰符 作用范围
public 任何地方可见,默认值
private 只在当前文件可见
internal 同一模块可见(当前的项目中可以随意访问,与 public 没大差别,但是如果别人引用我们的项目,那么无法使用)
protected 顶层声明不能使用

类成员

修饰符 作用范围
public 任何地方可见,默认值
private 只在当前类内部可见
protected 当前类和子类可见
internal 同一模块可见
kotlin 复制代码
class Student(
    private var name: String,
    internal var age: Int
) {
    private constructor() : this("", 10)

    fun show() {
        println(name)  // 小明
    }
}

fun main() {
    val stu = Student("小明", 18)
    stu.show()
    println(stu.age)  // 18
    // println(stu.name)  // 编译错误,name是private
}

3.封装、继承和多态

封装、继承、多态是面向对象的基础思想,Kotlin 与 Java 思路一致,但语法更简洁。

  • 封装:隐藏内部属性,通过方法暴露必要操作。
  • 继承:子类复用父类能力,并扩展新能力。
  • 多态:父类引用指向子类对象,运行时调用子类重写后的方法。

3.1 封装

封装通常使用 private 隐藏属性,再提供方法控制访问。

kotlin 复制代码
class Student(private var name: String, private var age: Int) {
    fun getName(): String = name
    fun getAge(): Int = age

    fun setName(name: String) {
        this.name = name
    }
}

fun main() {
    val stu = Student("小明", 18)
    println(stu.getName())  // 小明
    stu.setName("小红")
    println(stu.getName())  // 小红
}

Kotlin 中也可以使用属性 getter/setter 实现更自然的封装。

kotlin 复制代码
class Student(name: String) {
    var name: String = name
        private set
}

fun main() {
    val stu = Student("小明")
    println(stu.name)  // 小明
    // stu.name = "小红"  // 编译错误,setter是private
}

3.2 继承

Kotlin 类默认不能被继承,相当于 Java 中默认 final。如果允许继承,需要使用 open

kotlin 复制代码
open class Student {
    val card = "学生证"
    fun hello() = println("我会打招呼")  // 我会打招呼
}

class ArtStudent : Student() {
    fun draw() = println("我会画画")  // 我会画画
}

fun main() {
    val stu = ArtStudent()
    stu.hello()
    stu.draw()
}

父类有主构造函数时,子类必须调用父类构造函数。

kotlin 复制代码
open class Student(val name: String, val age: Int)

// 子类必须适配其父类的构造函数,因为需要先对父类进行初始化
// 其实就是去调用一次父类的构造函数,填入需要的参数即可,这里的参数可以是当前子类构造方法的形参,也可以是直接填写的一个参数
class ArtStudent(name: String, age: Int) : Student(name, age) {
    fun show() = println("$name $age")
}

fun main() {
    val stu = ArtStudent("小明", 18)
    stu.show()  // 小明 18
}

如果父类有多个构造函数,子类可以选择其中一个。

kotlin 复制代码
open class Student {
    constructor(name: String) {
        println(name)  // 小明
    }

    constructor(name: String, age: Int) {
        println("$name $age")
    }
}

class ArtStudent : Student("小明")

fun main() {
    ArtStudent()
}

子类没有主构造函数时,次要构造函数使用 super 调用父类构造函数。

kotlin 复制代码
open class Student {
    constructor(name: String) {
        println("父类构造:$name")  // 父类构造:小明
    }
}

// 子类不写主构造函数时,可以直接在冒号后面添加父类类名
class ArtStudent : Student {
    constructor(name: String) : super(name)
}

fun main() {
    ArtStudent("小明")
}

子类也可以用 this 调用自己的其他构造函数,但最终仍然要调用父类构造函数。

kotlin 复制代码
open class Student {
    constructor(name: String, age: Int) {
        println("$name $age")  // 小明 18
    }
}

class ArtStudent : Student {
    constructor(name: String) : this(name, 18)
    constructor(name: String, age: Int) : super(name, age)
}

fun main() {
    ArtStudent("小明")
}

如果子类有主构造函数,次要构造函数必须直接或间接调用自己的主构造函数,不能绕过主构造函数直接调用父类构造函数。

kotlin 复制代码
open class Student() {
    constructor(str: String) : this()
    constructor(str: String, age: Int) : this()
    fun hello() = println("我会打招呼")
}

class ArtStudent() : Student() {
    constructor(str: String) : this()  //正确,必须直接或间接调用主构造函数
    constructor(str: String, age: Int) : super(str, age)   //报错,不能绕过主构造函数去匹配父类构造函数
    fun draw() = println("我会画画")
}

3.3 属性覆盖

Kotlin 中方法和属性默认不能被重写。父类成员要加 open,子类重写时加 override

kotlin 复制代码
open class Student {
    open fun hello() = println("我会打招呼")
}

class ArtStudent : Student() {
    override fun hello() = println("哦哈哟")
}

fun main() {
    val stu = ArtStudent()
    stu.hello()  // 哦哈哟
}

属性也可以被覆盖。

kotlin 复制代码
open class Student {
    open val type: String = "普通学生"
    fun show() = println(type)
}

class ArtStudent : Student() {
    override val type: String = "美术生"
}

fun main() {
    val stu = ArtStudent()
    stu.show()  // 美术生
}

也可以 在子类主构造函数中直接覆盖父类属性

kotlin 复制代码
open class Student {
    open val name: String = "大明"
}

// 在主构造函数中覆盖,也是可以的,这样会将构造时传入的值进行覆盖
class ArtStudent(override val name: String) : Student()

fun main() {
    val stu = ArtStudent("小明")
    println(stu.name)  // 小明
}

!note

注意:父类初始化早于子类初始化,不建议在父类 init 中访问可被子类覆盖的属性,可能出现未初始化问题。

kotlin 复制代码
open class Student {
    open val name: String = "小明"
    init { println("我的名字是: ${name.length}") }  //这里拿到的name实际上是还未初始化的子类name
}

class ArtStudent : Student() {
    override val name = "大明"
}

fun main() {
    var student = ArtStudent()
}

由于父类初始化在子类之前,此时子类还未开始初始化,其覆盖的属性此时没有初始值,根据不同平台的实现,可能会出现一些问题,比如 JVM 平台下,没有初始化的对象引用默认为 null,那么这里就会直接报空指针异常。

父类属性初始化 → 父类 init → 子类属性初始化 → 子类 init

3.4 多态

多态:父类引用指向子类对象,子类可以重写父类的 open 方法,运行时根据实际对象类型调用对应的方法。

kotlin 复制代码
open class Student {
    open fun hello() = println("大家好")
}

class ArtStudent : Student() {
    override fun hello() = println("我是美术生")
}

class ScienceStudent : Student() {
    override fun hello() = println("我是理科生")
}

fun main() {
    val a: Student = ArtStudent()
    val b: Student = ScienceStudent()
    a.hello()  // 我是美术生
    b.hello()  // 我是理科生
}

使用 is 判断对象类型。

kotlin 复制代码
open class Student
class ArtStudent : Student()
class SportStudent : Student()

fun main() {
    val student: Student = ArtStudent()
    println(student is ArtStudent)    // true
    println(student is SportStudent)  // false
    println(student is Student)       // true
}

使用 as 强制类型转换。建议先用 is 判断,避免类型转换异常。

kotlin 复制代码
open class Student
class ArtStudent : Student() {
    fun draw() = println("我会画画")  // 我会画画
}

fun main() {
    val student: Student = ArtStudent()
    if (student is ArtStudent) {
        val artStudent = student as ArtStudent
        artStudent.draw()
    }
}

Kotlin 有智能类型转换,is 判断之后通常不需要再写 as

kotlin 复制代码
fun main() {
    val student: Student = ArtStudent()
    if (student is ArtStudent) {
        student.draw()  // 我会画画
    }
}

3.5 顶层 Any 类

Kotlin 中所有类默认继承自 Any,类似 Java 中的 Object,但成员更少。

Any 主要包含三个函数:

kotlin 复制代码
open class Any {
    open operator fun equals(other: Any?): Boolean
    open fun hashCode(): Int
    open fun toString(): String
}

Kotlin 中有两种相等判断:

写法 含义
== 结构相等,等价于调用 equals()
=== 引用相等,判断是否是同一个对象
kotlin 复制代码
fun main() {
    val a = String(charArrayOf('H', 'i'))
    val b = String(charArrayOf('H', 'i'))

    println(a == b)   // true
    println(a === b)  // false
}

toString() 默认输出类名和哈希信息,可以重写。

kotlin 复制代码
class Student(val name: String) {
    override fun toString(): String {
        return "Student(name=$name)"
    }
}

fun main() {
    println(Student("小明"))  // Student(name=小明)
}

3.6 抽象类

抽象类使用 abstract 声明,不能直接创建对象。抽象成员没有函数体,子类必须实现。

kotlin 复制代码
abstract class Student {
    // 注意抽象的属性不能为private,不然子类就没法重写了
    abstract val type: String // 抽象类中可以存在抽象成员属性
    abstract fun hello() // 抽象类中可以存在抽象函数
}

// 子类继承自抽象类时,必须要重写抽象类中定义的抽象属性和抽象函数
class ArtStudent : Student() {
    override val type: String = "美术生"
    override fun hello() = println("大家好,我是$type")
}

fun main() {
    val stu = ArtStudent()
    stu.hello()  // 大家好,我是美术生
}

抽象类也可以有普通属性和普通函数。

kotlin 复制代码
abstract class Student {
    abstract fun hello()
    fun test() = println("普通函数")  // 普通函数
}

class ArtStudent : Student() {
    override fun hello() = println("抽象函数的实现")  // 抽象函数的实现
}

fun main() {
    val stu = ArtStudent()
    stu.test()
    stu.hello()
}

3.7 接口

接口使用 interface 声明。类可以继承一个父类,同时实现多个接口。

kotlin 复制代码
interface A {
    val x: String // 接口中所有属性默认都是abstract的(可省略关键字)
    fun sleep() // 接口中所有函数默认都是abstract的(可省略关键字)
}

interface B {
    fun game()
}

class Student : A, B {
    override val x: String = "测试"
    override fun sleep() = println("睡觉")
    override fun game() = println("打游戏")
}

fun main() {
    val stu = Student()
    println(stu.x)  // 测试
    stu.sleep()     // 睡觉
    stu.game()      // 打游戏
}

接口中的函数可以有默认实现。

kotlin 复制代码
interface A {
    fun sleep() = println("默认睡觉")
}

class Student : A

fun main() {
    val stu = Student()
    stu.sleep()  // 默认睡觉
}

如果多个接口有同名默认方法,子类必须重写并明确选择调用哪个父接口的方法。

kotlin 复制代码
interface A {
    fun hello() = println("A")
}

interface B {
    fun hello() = println("B")
}

class Student : A, B {
    override fun hello() {
        super<A>.hello()  // A
        super<B>.hello()  // B
    }
}

fun main() {
    Student().hello()
}

3.8 类的扩展

扩展函数可以在不修改原类源码的情况下,为已有类"增加"函数。它本质是静态分发的工具函数,不是真的改了原类。

kotlin 复制代码
fun String.test(): String = "666"

fun main() {
    val text = "Hello World"
    println(text.test())  // 666
}

扩展函数是静态分发,具体调用哪个扩展函数取决于变量的声明类型,而不是运行时实际类型。

kotlin 复制代码
open class Shape
class Rectangle : Shape()

fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"

fun printClassName(s: Shape) {
    println(s.getName())  // Shape
}

fun main() {
    printClassName(Rectangle())
}

直接扩展属性是不允许的,扩展属性不能真正存储字段,因此必须提供 getter,不能直接拥有field字段。

kotlin 复制代码
class Student

val Student.gender: String
    get() = "未知"

fun main() {
    val stu = Student()
    println(stu.gender)  // 未知
}

扩展函数内部的 this 指向被扩展的对象。如果和外部类成员重名,需要使用 this@外部类名 区分。

kotlin 复制代码
class A {
    fun hello() = "Hello World"
}

class B(private val a: A) {
    private fun A.test() {
        println(this.hello())   // Hello World 扩展函数中的 this 依然指的是被扩展的类对象
        println(this@B.hello()) // Bye World
    }

    fun hello() = "Bye World"
}

将一个扩展函数作为参数给到一个函数类型变量,扩展函数类型需要写成 类型.() -> 返回值类型

kotlin 复制代码
fun main() {
    val func: String.() -> Int = {
        this.length
    }

    println("Hello".func())  // 5
    println(func("Kotlin"))  // 6
}

✨✨✨