Kotlin面向对象之class和继承
class是面向对象编程的核心
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()
好在我们不用关心具体实现细节,只要知道怎么用就行了
更多推荐

所有评论(0)