class(类)

class是面向对象编程的核心。

class定义 

在kotlin中,class可以在一个单独的文件中,也可以和函数或变量定义在一起。

kotlin定义一个class非常简单:

class Weapon

kotlin中,一个空的class 可以不需要{}

实例化class

kotlin实例化类不需要new关键字

val weapon = Weapon() 

类定义主要包含两类内容:行为和数据

定义类的行为就是在类结构体里添加函数定义
定义在类结构体里的函数叫做类函数:

    val range:Int = 2
    fun printRange()="range: $range"

初始值 

类属性必须拥有初始值
 

var name:string //由于没有初始值,所以这样的定义无效

可见性修饰符

kotlin支持4种可见性修饰符:

private      函数或属性仅在当前类中可见
protected  函数或属性仅在当前类或子类中可见
public        函数或属性对外部可见 无修饰符情况下,默认是public
internal     函数或属性仅在同一模块可见,这种可见性在开发kotlin库文件时非常有用
kotlin不支持同一包内private可见性

protected val durability = 10

类属性操作:Field、setter和getter

针对定义的每个属性,kotlin都会产生一个field、一个getter和一个setter方法
field用于存放数据,不能直接操作field
field数据值暴露给getter和setter
getter决定如何读取属性值

get() = field.uppercase() // 自定义get方法,将字母转换为大写

 而使用.语法调用class属性其实是调用getter

println(weapon.name)


setter决定如何给属性赋值,且只有var关键字定义的属性才有setter方法

    set(value) { // 自定义set方法,保存属性前,转换为小写
        field = value.lowercase()
    }

使用=等赋值运算操作符其实是调用setter

weapon.range += 10 // 设置武器射程

属性定义属于类级别的
也就是说,只要没有可见性限制,某个类中定义的属性都会被其它类访问到
getter和setter方法提供了比较细致的属性操作
所有的var都有setter方法
默认情况下,getter和setter方法可见性与属性一致
kotlin支持为属性的setter方法提供自定义可见性

    var name:String = "Italian Pao"
        protected set(value){ // 自定义setter可见性,使属性只能在当前class中修改
            field = value.lowercase()
        }

以上代码setter设置好后,在class外部修改name值就会抛出异常

不过需要注意的是,属性的getter和setter可见性不能比属性本身更宽松

private val hp = 10
        // protected get() = field + 10  会抛出异常 Getter visibility must be the same as property visibility

通常情况下,定义类属性时,kotlin都会创建一个field来存储属性值
但有一种特殊情况:计算属性
计算属性时通过一个覆盖的get或者set运算符来定义的,
计算属性不会创建field

    val forceAtk  // 攻击敌方时的实际攻击值
        get() = (1..force).shuffled().first() //武器标准攻击力范围内的随机值 

如果一个类的属性既可空,又可变
那么引用之前一定要保证它非空

竞态条件

先看一个例子:

if(weapon != null){
     println(weapon.name)
}
// 运行抛出异常
// Kotlin: Smart cast to 'Weapon' is impossible, because 'weapon' is a mutable property that could have been changed by this time

上面的if语句中,尽管函数中做了空值检查,但编译器认为检查通过到打印名称之前的这段时间内,weapon属性仍有可能被改为null
所以weapon属性可能为null,但同时又不能像正常情况下那样,在空值检查里对weapon做强制类型转换,
这个问题被称作竞态条件(race condition)问题
要解决这个问题,其中一个办法是使用also标准函数来防范空值:

weapon?.also {
    println(it.name)
}

    also中。 it作为匿名函数的作用域内的局部变量,因此外部条件无法修改,
    并且also函数是在安全调用操作符"?."之后,因此强制类型转换问题也完全避免了

继承

在kotlin中,继承一个类使用:操作符

子类使用“:”操作符继承父类
”:“右边是被继承的父类构造函数

现在创建一个意大利炮的class,让它继承自刚才的基本武器类

可以看到,编译器直接提示错误

这是因为Kotlin中,Class的默认权限是final,即不允许被其它类继承

要想开放子类继承,必须使用open关键字修饰

open class Weapon(val _name:String)

现在错误提示被取消了

重写

同样,Kotlin的属性和方法默认也都是final,

如果想要在子类中重写属性或方法,父级属性必须使用open关键字修饰

    open val range:Int = 2 // 使用open关键字修Weapon类中的武器射程属性
    fun printRange()="range: $range"

子类重写父类属性或方法,必须使用override关键字修饰

在Weapon中定义一个默认的武器耐久:

protected var durability = 10 // 武器耐久度
open fun durability(loss:Int=3) {

    durability -= (0..loss).shuffled().first()
}

由于意大利炮每次只能消耗一发炮弹,所以上面的武器耐久消耗显然不能适用意大利炮的

假设李云龙部队当前每次只能带2发炮弹

    override var durability = 2 // 重写武器耐久度
    override fun durabilityLoss(loss:Int) { // 重写武器耐久消耗
        if(durability==0) throw Exception("No Ammunition")
        durability-= loss
    }

子类需要引用父类的属性或方法,需要使用super关键字

override val range: Int = super.range + 10 //重写意大利炮武器射程

 避免重新

在kotlin中,子类覆盖一个父级函数时,这个函数也允许被当前类的子类覆盖,
想要避免这个问题,使用final关键字修饰即可

比如,我们不希望意大利炮的子类修改每发炮弹的消耗

    final override fun durabilityLoss(loss:Int) { // 重写武器耐久消耗逻辑
        if(durability==0) throw Exception("No Ammunition")
        durability-= 1
    }

多态

父子类都拥有一个durabilityLoss函数,具体调用哪个要看是基于哪个类调用这个函数
这种用法涉及到面向对象的多态概念
多态特性可以有效简化代码结构
使用多态能在一组class中复用个性化函数
例如,在ItalianPao中覆盖durabilityLoss函数时,便定义了一个新版的durabilityLoss函数
因此在这里调用的即是ItalianPao中的durabilityLoss

val italianPao:Weapon = ItalianPao()
val italianPao:ItalianPao = ItalianPao()

 上面两行代码都能正常运行,

    println(italianPao is Weapon) // true
    println(italianPao is ItalianPao) //true

 我们发现,使用is关键字判断italian对象类型时,两种类型都能检查通过

因为ItalianPao是Weapon的子类,所以变量italianPao既是Weapon类型,也是ItalianPao类型

    val weapon = Weapon("三八 Rifle")
    // println(weapon is Weapon) //true
    println(weapon is ItalianPao) // false

但weapon本身不是Italian类型的对象,所以上面这条检查不会被通过

Any

kotlin中,所有类都继承自一个叫做Any的父类

val weapon:Any = Weapon("三八 Rifle")
val italianPao:Any = ItalianPao()

所以在kotlin中
类的层级关系实际是 Any->Weapon->ItalianPao

要在控制台打印某个变量的值
kotlin会直接调用一个toString()方法来决定打印到控制台的值应该是什么样的
对于String或Int这种简单的对象,toString()非常方便
但对于复杂结构的对象,使用toString()展示就显得吃力了
好在对于类似toString()这样简单的函数,Any提供了抽象定义

我们可以在任何类中自定义toString()方法,
以便展示我们需要的信息和效果
值得一提的是,Any类的抽象定义是多平台的,
即代码运行在哪个平台,就有与该平台对应的实现版本
比如项目在JVM平台运行,那么toString()实现的版本就 java.lang.Object.toString
而在JavaScript平台代码中,又会有其它方式实现toString()
好在我们不用关心具体实现细节,只要知道怎么用就行了

Logo

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

更多推荐