目录

继承与重载的open关键字

类型检测

类型转化

智能类型转换

Any超类

对象

object关键字

对象声明

对象表达式

伴生对象

嵌套类

数据类

重写equals和hashCode

copy

解构声明

运算符重载

枚举类

枚举类定义函数

代数数据类型

密封类


继承与重载的open关键字

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

open class Product(val name:String){
    fun description() = "Product: $name"

    open fun load() = "Nothing"  //父类函数open子类才能override
}

class LuxureProduct : Product("Luxure"){
    override fun load() = "Luxure"  //不允许直接覆盖父类的函数
}

fun main(){
    val p = LuxureProduct()
    println(p.load())   //Luxure
}

类open,子类才能继承;父类函数open,子类才能override。

类型检测

Kotlin的is运算符是个不错的工具,可以用来检查某个对象的类型。

    println(p is Product)     //true
    println(p is LuxureProduct)    //true
    println(p is File)     //false

p的类型既是父类的类型又是子类的类型。

类型转化

Kotlin中可使用as来类型转换

如果在子类中有一个特殊的函数special(),p无法直接调用special()函数,此时可以先使用is 类型检测,然后使用as类型转化。

if(p is LuxureProduct){
//        p as LuxureProduct  //可省略
        p.special()   //判断后自动转换为LuxureProduct,调用子类的特殊方法
    }

目前,当完成类型is判断后,自动进行了类型转换,无需as操作了。

智能类型转换

当不使用is判断时,直接使用as类型转换,Kotlin编译器允许只转换一次,后续使用无需转换直接使用。

Any超类

无须在代码里显示指定,每一个类都会继承一个共同的叫做Any的超类。

可使用 p is Any 判断,所有类继承自Any超类。

对象

object关键字

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

使用object关键字有三种方式

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

对象声明

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

object Application{
    init {
        println("Initializing application...")
    }
    fun doSomething(){
        println("doSomething")
    }
}

fun main(){
    Application.doSomething()   //既是类名,也是实例名  doSomething
    println(Application)   //Application@27716f4
    println(Application)   //Application@27716f4
}

既是类名也是实例名,可直接调用函数。

对象表达式

有时候不一定非要定义一个新的命名类不可,也许你需要某个现有类的一种变体实例,但只需用一次就行了,对于这种用完就丢的类实例,连命名都可以省了。这个对象表达式是xxx的子类,这个匿名类依然遵循object关键字的一个规则,即一旦实例化,该匿名类只能有唯一一个实例存在。

open class Players{
    open fun load() = "Loading..."
}

fun main(){
    val p = object : Players(){   //一个仅用一次的 基于Players类的 重写load函数的 子类 实例p
        override fun load() = "anonymous nothing..."
    }
    println(p.load())   //open fun load() = "Loading..."
}

一个简易版子类。

伴生对象

如果你想将某个对象的初始化和一个类实例捆绑在一起,可以考虑使用伴生对象,使用companion修饰符,你可以在一个类定义里声明一个伴生对象,一个类里只能有一个伴生对象。

import java.io.File

open class ConfigMap{
    companion object{
        private const val path = "D:\\test1.txt"

        fun load() = File(path).readBytes()
    }
}

fun main(){
    ConfigMap.load()
}

使用雷同object

嵌套类

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

class Player6{
    class Equipment(var name: String){     //类中类
        fun show(){
            println("Equipment: $name")
        }
    }
    fun battle(){}
}

fun main(){
    Player6.Equipment("sharp").show()
}

注意一下对于类中类的初始化方式。

数据类

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

数据类提供了toString的个性化实现

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

重写equals和hashCode

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

fun main(){
    println(Coordinate(0,0))

    //== 比较内容, === 比较引用,是否指向同内存区域
    println(Coordinate(0,0) == Coordinate(0,0))  //true   data类重写了equals()
}

copy

除了重写Any类的部分函数,提供更好用的默认实现外,数据类还提供了一个函数,它可以用来方便地复制另一个对象。假设你想创建一个Student实例,除了name属性,它拥有和另一个现有的Student实例完全一样的属性值,如果Student是个数据类,那么复制现有的Student实例就很简单了,只要调用copy函数,给想修改的属性传入值参就可以了。

data class Student(var name: String, var age: Int){
    private val hobby = "music"
    val subject:String
    init {
        println("initializing student")
        subject = "math"
    }
    constructor(_name:String) :this(_name,10)
}

fun main(){
    val s = Student("Jack")
    val copy = s.copy("Rose")
    println(s)
    println(copy)
}

明显调用了两次Student,同时只改变了name。

如果想要修改toString:

右键 generate tostring

注意:在copy的时候使用的是主构造函数new了一个新的对象,因此如果在次构造函数里的赋值对copy不生效。

解构声明

解构声明的后台实现就是声明component1...等若干个组件函数,让每个函数负责管理你想返回的一个属性数据,如果你定义一个数据类,它会自动为所有定义在主构造函数的属性添加对应的组件函数。

class PlayerScore(val experience:Int,val level:Int){
    operator fun component1():Int = experience
    operator fun component2():Int = level
}

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

data class 直接支持解构语法,不需要自己写。

运算符重载

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

和C++相似。

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

fun main(){
   val c1 = Coordinate2(0,0)
    val c2 = Coordinate2(5,10)
    println(c1 + c2)   //Coordinate2(x=5, y=10)
} 

枚举类

简单实现:

enum class Direction {
    EAST,
    SOUTH,
    WEST,
    NORTH
}
fun main(){
    println(Direction.EAST)    //EAST
    println(Direction.EAST is Direction)   //true
}

枚举类定义函数

enum class Direction2(private val coordinate: Coordinate) {
    EAST(Coordinate(1,0)),
    SOUTH(Coordinate(-1,0)),
    WEST(Coordinate(0,-1)),
    NORTH(Coordinate(0,1));

    fun updateCoordinate(playerCoordinate: Coordinate) = Coordinate(playerCoordinate.x + coordinate.x,
        playerCoordinate.y + coordinate.y)
}
fun main(){
   println(Direction2.EAST.updateCoordinate(Coordinate(10,20)))  //11,20
}

Direction2这个枚举类里面的EAST可以看作是一个类,

updateCoordinate这个函数实现了将playerCoordinate的x,y分别与coordinate的x,y相加。

代数数据类型

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

enum class LicenseStatus {
    UNQUALIFIED,
    LEARNING,
    QUALIFIED,
}

class Driver(var status: LicenseStatus){
    fun checkLicence():String{
        return when(status){
            LicenseStatus.UNQUALIFIED -> "Licence not found"
            LicenseStatus.LEARNING -> "Licence already in progress"
            LicenseStatus.QUALIFIED -> "Licence already in progress"
        }
    }
}

fun main(){
    println(Driver(LicenseStatus.LEARNING).checkLicence())
}

好用,就像一个变量一样。其实我也可以直接写val status = .....

正如我刚知道我的舍友现在下午17:09要睡一个午觉一样........不理解但尊重。

虽然我和另一个舍友觉得她不会睡觉,但还是等她20min再去吃饭吧,宠她。

密封类

  • 对于更复杂的ADT,可以使用Kotlin的密封类来实现更复杂的定义,密封类可以用来定义一个类似于枚举类的ADT,但你可以更灵活地控制某个子类型。
  • 密封类可以有若干个子类,要继承密封类,这些子类必须和它定义在同一个文件里。
sealed class LicenseStatus2 {  //密封类
    object UNQUALIFIED : LicenseStatus2()
    object LEARNING : LicenseStatus2()
    class QUALIFIED(val licenceId:String) : LicenseStatus2()
}

class Driver2(var status: LicenseStatus2){
    fun checkLicence():String{
        return when(status){
            is LicenseStatus2.UNQUALIFIED -> "Licence not found"
            is LicenseStatus2.LEARNING -> "Licence already in progress"
            is LicenseStatus2.QUALIFIED -> "Licence already in progress, licenceId: ${(this.status as LicenseStatus2.QUALIFIED).licenceId}"
        }
    }
}

fun main(){
    val status = LicenseStatus2.QUALIFIED("123456")
    val driver = Driver2(status)
    println(driver.checkLicence())     //Licence already in progress, licenceId: 123456
}

没太听明白,只知道子类直接写在密封类里面......

他还说他没见有一个讲清楚的,自认为自己讲的很清楚,弹幕:我发现你也没讲清楚。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐