Kotlin 面向对象核心语法:类、继承、数据类与特殊类全解析
目录
类
继承与重载的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
}
没太听明白,只知道子类直接写在密封类里面......
他还说他没见有一个讲清楚的,自认为自己讲的很清楚,弹幕:我发现你也没讲清楚。
更多推荐


所有评论(0)