Kotlin面向对象_继承与对象

本文最后更新于:2022年11月30日 晚上

面向对象

继承

open关键字

类默认都是封闭的,要让某个类开放继承,必须使用open关键字修饰它。

open class Product(val name: String) {
    fun desc() = "Product: $name"
    open fun load() = "Nothing"
}

class LuxuryProduct : Product("Luxury") {
    // 重写方法
    override fun load() = "LuxuryProduct loading..."
}

fun main() {
    val p:Product = LuxuryProduct()
    println(p.load()) // LuxuryProduct loading...
}

类型检测与转换

使用is进行类型检测
使用as进行类型转换

import java.io.File

open class Product(val name: String) {
    fun desc() = "Product: $name"
    open fun load() = "Nothing"
}

class LuxuryProduct : Product("Luxury") {
    override fun load() = "LuxuryProduct loading..."
    fun special() = "LuxuryProduct special function"
}

fun main() {
    val p: Product = LuxuryProduct()
    println(p.load())
    // 类型检测
    println(p is Product) // true
    println(p is LuxuryProduct) // true
    println(p is File) // false

    if (p is LuxuryProduct) {
        // 类型转换
        println((p as LuxuryProduct).special()) // LuxuryProduct special function
    }
}

智能类型转换

Kotlin编译器很聪明,只要能确定any is 父类条件检查属实,它就会将any当做子类类型对待,因此,编译器允许你不经类型转换直接使用。

println((p as LuxuryProduct).special())
p.special() // 再次调用无需转换

Any超类

类似Java里的Object,Kotlin所有类的共同超类为Any

println(p is Any) // true

对象

Object关键字

使用object关键字,你可以定义一个只能产生一个实例的类-单例

使用object关键字有三种方式

  • 对象声明
  • 对象表达式
  • 伴生对象

对象声明

对象声明有利于组织代码和管理状态,尤其是管理整个应用运行生命周期内的某些一致性状态。

object ApplicationConfig {
    init {
        println("ApplicationConfig is loading...")
    }

    fun doSomething() {
        println("do something...")
    }
}

fun main() {
    // 单例模式 类名=对象名
    ApplicationConfig.doSomething()
    println(ApplicationConfig) // ApplicationConfig@52cc8049
    println(ApplicationConfig) //ApplicationConfig@52cc8049
}

对象表达式

有时候你不一定非要定义一个新的命名类不可,也许你需要某个现有类的一种变体实例,但只需用一次就行了,对于这种用完就丢的类实例,连命名都可以省了。

这个匿名类依然遵循object关键字的一个规则,即一旦实例化,该匿名类只能有唯一一个实例存在。

open class Player {
    open fun load() = "loading something..."
}

fun main() {
    // 对象表达式
    val p = object : Player() {
        override fun load(): String = "anonymous class load..."
    }
    println(p.load())
}

伴生对象

使用companion修饰符,将对象伴生在类上,一个类只能有一个伴生对象。

import java.io.File

open class ConfigMap {
    companion object {
        private const val PATH = "E://demo.txt"
        fun load() = File(PATH).readBytes()
    }
}

fun main() {
    ConfigMap.load()
}

嵌套类

如果一个类只对另一个类有用,那么将其嵌入到该类中并使这两个类保持在一起是合乎逻辑的,可以使用嵌套类。

class Play {
    class Equipment(var name: String) {
        fun show() = println("equipment: $name")
    }

    fun battle() {
        Equipment("sharp knife").show()
    }
}

fun main() {
    // 直接调用
    Play.Equipment("M416").show() // equipment: M416
}

数据类

数据类,是专门设计用来存储数据的类

  • 数据类提供了toString的个性化实现
  • ==符号默认情况下,比较对象就是比较它们的引用值,数据类提供了equalshashCode的个性化实现

使用数据类

data class Coordinate(var x: Int, var y: Int) {
    val isInBounds = x > 0 && y > 0
}

fun main() {
    println(Coordinate(10, 20))
    println(Coordinate(x = 10, y = 20) == Coordinate(x = 10, y = 20))
}
// Coordinate(x=10, y=20)
// true

使用普通类

class Coordinate(var x: Int, var y: Int) {
    val isInBounds = x > 0 && y > 0
}

fun main() {
    println(Coordinate(10, 20))
    println(Coordinate(x = 10, y = 20) == Coordinate(x = 10, y = 20))
}
// Coordinate@52cc8049
// false

正是因为上述这些特性,你才倾向于用数据类来表示存储数据的简单对象,对于那些经常需要比较、复制或打印自身内容的类,数据类尤其适合它们。然而,一个类要成为数据类,也要符合一定条件。总结下来,主要有三个方面:

  • 数据类必须有至少带一个参数的主构造函数
  • 数据类主构造函数的参数必须是val或var
  • 数据类不能使用abstract、open、sealed和inner修饰符

copy关键字

data还提供了复制对象的方法copy。

但是,copy时不会执行次构造函数里的语句,所以次构造函数里的赋值不会被copy到新对象。

data class Student(var name: String, val age: Int) {
    var score = 10
    private val bobby = "music"
    var subject: String

    init {
        println("initializing subject")
        subject = "math"
    }

    constructor(_name: String) : this(_name, 10) {
        println("constructor")
        score = 20
    }

    override fun toString(): String {
        return "Student(name='$name', age=$age, score=$score, bobby='$bobby', subject='$subject')"
    }
}

fun main() {
    val s = Student("John")
    val copy = s.copy("Alice")
    println(s)
    println(copy)
}
// initializing subject
// constructor
// initializing subject
// Student(name='John', age=10, score=20, bobby='music', subject='math')
// Student(name='Alice', age=10, score=10, bobby='music', subject='math')

结构声明

解构声明的后台实现就是声明component1、component2等若干个组件函数,让每个函数负责管理你想返回的一个属性数据。

普通类

class PlayerScore(val exp: Int, val level: Int) {
    // 需要组件函数
    operator fun component1() = exp
    operator fun component2() = level
}

fun main() {
    val (x, y) = PlayerScore(10, 20)
    println("$x,$y") // 10,20

如果你定义一个数据类,它会自动为所有定义在主构造函数的属性添加对应的组件函数。

data class PlayerScore(val exp: Int, val level: Int) {}

fun main() {
    val (x, y) = PlayerScore(10, 20)
    println("$x,$y") // 10,20
}

运算符重载

如果要将内置运算符应用在自定义类身上,你必须重写运算符函数,告诉编译器该如何操作自定义类。

data class Coordinate(var x: Int, var y: Int) {
    val isInBounds = x > 0 && y > 0
    // 重载加法运算符
    operator fun plus(other: Coordinate) = Coordinate(x + other.x, y + other.y)
}

fun main() {
    val p1 = Coordinate(10, 20)
    val p2 = Coordinate(20, 10)
    val p3 = p1 + p2
    println(p3) // Coordinate(x=30, y=30)
}

常见操作符

操作符 函数名 作用
+ plus 把一个对象添加到另一个对象里
+= plusAssign 把一个对象添加到另一个对象里,然后将结果赋值给第一个对象
== equals 如果两个对象相等,则返回true,否则返回false
> compareTo 如果左边的对象大于右边的对象,则返回true,否则返回false
[ ] get 返回集合中指定位置的元素
.. rangeTo 创建一个range对象
in contains 如果对象包含在集合里,则返回true

枚举类

枚举类,用来定义常量集合的一种特殊类。

enum class Direction {
    EAST,
    WEST,
    SOUTH,
    NORTH
}

fun main() {
    println(Direction.EAST) // EAST
    println(Direction.EAST is Direction) // true
}

枚举类定义函数

enum class Direction(private val coordinate: Coordinate) {
    // 实例
    EAST(Coordinate(1, 0)),
    WEST(Coordinate(-1, 0)),
    SOUTH(Coordinate(0, -1)),
    NORTH(Coordinate(0, 1));

    fun updateCoordinate(playerCoordinate: Coordinate) =
        Coordinate(playerCoordinate.x + coordinate.x, playerCoordinate.y + coordinate.y)
}

fun main() {
    println(Direction.EAST.updateCoordinate(Coordinate(10, 20)))
}
// Coordinate(x=11, y=20)

代数数据类型

可以用来表示一组子类型的闭集,枚举类就是一种简单的ADT。

enum class LicenseStatus {
    UNQUALIFIED,
    LEARNING,
    QUALIFIED;
}

class Driver(var status: LicenseStatus) {
    fun checkLicense(): String {
        return when (status) {
            LicenseStatus.UNQUALIFIED -> "没资格"
            LicenseStatus.LEARNING -> "正在学"
            LicenseStatus.QUALIFIED -> "有资格"
        }
    }
}

fun main() {
    println(Driver(LicenseStatus.QUALIFIED).checkLicense())
}

密封类

对于更复杂的ADT,你可以使用Kotlin的密封类(sealed class)来实现更复杂的定义,密封类可以用来定义一个类似于枚举类的ADT,但你可以更灵活地控制某个子类型。

密封类可以有若干个子类,要继承密封类,这些子类必须和它定义在同一个文件里。

sealed class LicenseStatus {
    // 没有属性,所以用单例模式
    object UnQualified : LicenseStatus()
    object Learning : LicenseStatus()
    // 每次id不一样,所以使用类
    class Qualified(val licenseID: String) : LicenseStatus()
}

class Driver(var status: LicenseStatus) {
    fun checkLicense(): String {
        return when (status) {
            is LicenseStatus.UnQualified -> "没资格"
            is LicenseStatus.Learning -> "正在学"
            is LicenseStatus.Qualified -> "有资格,驾驶证编号:${(this.status as LicenseStatus.Qualified).licenseID}"
        }
    }
}

fun main() {
    val status = LicenseStatus.Qualified("2225211")
    val driver = Driver(status)
    println(driver.checkLicense())
    // 有资格,驾驶证编号:2225211
}

Kotlin面向对象_继承与对象
http://yorick.love/2022/09/11/Kotlin/Kotlin面向对象_继承与对象/
作者
Yorick
发布于
2022年9月11日
许可协议