继承
在 java 里如果一个类没有被 final 关键字修饰,那么它都是可以被继承的。而在 kotlin 中,类默认都是封闭的,要让某个类开放继承,必须使用 open 关键字修饰它,否则会编译报错。此外在子类中,如果要复写父类的某个方法,需要用到关键字 Override(在 kt 中就不是注解了)。并且被复写的方法也需要用 open 关键字来修饰。
open class Product(var name : String){fun description() = "Product: $name"open fun load() = "Nothing..."
}class LuxuryProduct : Product("Luxury"){override fun load(): String = "LuxuryProduct loading...."
}fun main() {val luxuryProduct : Product = LuxuryProduct()println(luxuryProduct.load())
}
类型转换
kotlin 的 is 运算符是个不错的工具,可以用来检查某个对象的类型。
val p : Product = LuxuryProduct()println(p.load())// is 类型检测println(p is Product)println(p is LuxuryProduct)println(p is File)
类型转换,用到关键字 as
智能类型转换
kotlin 编译器很聪明,只要成功完成一次类型转换,编译器允许你不再经过类型转换而直接使用。
Any 超类
我们知道在 java 里面有一个超类 Object,它是所有类的父类。而在 kotlin 里,每一个类都会继承一个共同的叫做 Any 的超类,无需在代码里显示指定。
对象
Object 关键字
1. 使用 object 关键字,你可以定义一个只能产生一个实例的类-单例
2. 使用 object 关键字有三种方式
对象声明
对象表达式
半生对象
对象声明
对象声明有利于组织代码和管理状态,尤其是管理整个应用运行生命周期内的某些一致性状态。
打印它多次。结果都是同一个对象实例,所以是单例对象。
println(ApplicationConfig)println(ApplicationConfig)
对象表达式
有时候你不一定非要定义一个新的命名类不可,也许你需要某个现有类的一种变体实例,但只需用一次就行了。事实上,对于这种用完就丢的类实例,连命名都可以省了。这个对象表达式是 XX 的子类,这个匿名类依然遵循 object 关键字的一个规则,即一旦实例化,该匿名类只能有唯一一个实例存在。
open class Player{open fun load() = "load nothing..."
}fun main() {// 通过 object 声明对象。这种方式生成的是 Player() 的子类,然后用这个子类构造出对象 pval p = object : Player(){override fun load() = "anonymous nothing...."}println(p.load())
}
伴生对象
如果你想将某个对象的初始化和一个类实例捆绑在一起,可以考虑使用伴生对象,使用 companion 修饰符,你可以在一个类定义里声明一个伴生对象,一个类里只能有一个伴生对象。
import java.io.Fileopen class ConfigMap{companion object{private const val PATH = "https://..."fun load() = File(PATH).readBytes()}
}fun main() {ConfigMap.load()
}
只有初始化 ConfigMap 类或者调用了 load() 函数的时候,它里面的伴生对象(companino object)的内容才会载入。而且无论 ConfigMap 实例化了多少次,它里面的伴生对象都只有一份存在。
类
嵌套类
如果一个类只对另一个类有用,那么将其嵌入到该类中并使这两个类保持在一起是合乎逻辑的,可以使用嵌套类。
实例:装备和玩家。当我们需要装备时,肯定是需要玩家的。
class Player2{class Equipment(var name : String){fun show() = println("equipment $name")}fun battle(){Equipment("sharp knife").show()}
}fun main() {//TODO Player2 直接使用它内部的 Equipment 类,并调用 show() 方法Player2.Equipment("AK47").show()
}
*数据类*
数据类,是专门设计用来存储数据的类,用关键字 data 修饰;
数据类提供了 toString 的个性化实现;
通过 println() 打印该类
println(Coordinate(10, 20))
示例1:Coordiante() 类没有使用 data 关键字修饰
示例2:用 data 修饰,那么该类就提供了 toString() 的个性化实现
通过 show Kotlin Bytecode 查看 toString() 的默认实现。
== 符号默认情况下,比较对象就是比较它们的引用值,数据类提供了 equals 和 hashCode 的个性化实现。
我们通过 show Kotlin Bytecode 查看到,它还重写了 equals 和 hashCode 两个方法
copy
数据类还提供了一个比较好用的 copy 函数,它可以用来方便地复制一个对象。假设你想创建一个 Student 实例,除了 name 属性,它拥有和另一个现有 Student 实例完全一样的属性值,如果 Student 是个数据类,那么复制现有 Student 实例就很简单了,只要调用 copy 函数,给想修改的属性传入值参就可以了。
示例:
注意:因为用 copy 去构造一个对象时,创建了一个新的对象,它没有用到 次构造函数。所以 rose 的 score 就是 0。如下代码所示:
解构声明
这也是数据类一个非常好的特性。解构语法就是:一个集合里面有3个元素,我们直接要把这三个元素拿出来给3个变量赋值(注意:List 本身就支持解构语法)。如下所示:
val (x, y, z) = listOf(1, 2, 3)
示例1:普通类支持 解构语法,通过 operator fun component() 组合函数
示例2:数据类 直接支持 解构语法。
数据类背后自动帮我们生成了 operator。通过 show Kotlin Bytecode 查看
运算符重载
数据 经常就有 + 或者 - 这种数据操作,而当我们要对 数据类 进行数据那些类似的+或者-操作时,直接的 + 或者 - 肯定是不行的,如下代码所示:
所以,当我们没发对数据类进行普通的 + 或者 - 操作时,这时就需要运算符重载操作了。如果要进行 + 操作,那么就要重写这个 plus 函数
使用数据类的条件
正是因为上述这些特性,才倾向于用数据类来表示存储数据的简单对象,对于那些经常需要比较、复制或打印自身内容的类,数据类尤其适合它们。然而,一个类要成为数据类,也要符合一定条件。总结下来,主要有三个方面:
数据类必须有至少带一个参数的主构造函数;
数据类主构造函数的参数必须是 val 或 var;
数据类不能使用 abstract、open、sealed 和 inner 修饰符。
枚举类
枚举类,用来定义常量集合的一种特殊类。enmu 关键字修饰。在 java 里,enmu 关键字后面不会跟 class 关键字,而在 kotlin 里需要,表示它是一个枚举类。
注意:如果通过 Direction.EASE 来调用 EASE,那么这个 EASE 就是 Direction 的一个实例。
枚举类也可以定义函数。这就是与 java 里的枚举的差异。
枚举类可以提供构造函数,内部也可以声明函数。
代数数据类型
可以用来表示一组子类型的闭集,枚举类就是一种简单的代数数据类型(ADT)
enum class DriveLience {UNQUALIFIED,LEARNING,QUALIFIED;
}class Driver(val status : DriveLience){fun checkLicense() : String{return when (status){DriveLience.UNQUALIFIED -> "没有驾驶证"DriveLience.LEARNING -> "正在考证"DriveLience.QUALIFIED -> "有驾驶证"// 不需要 else 分支}}
}fun main() {println(Driver(DriveLience.LEARNING).checkLicense())
}
密封类
当有更复杂的需求,枚举类就显得不够了,例如当 Driver 有驾驶证时,我们需要打印出它的驾驶证 id,这种情况就需要用到密封类。
对于更复杂的 ADT,你可以使用 Kotlin 的密封类(sealed class)来实现更复杂的定义,密封类可以用来定义一个类似于枚举类的 ADT,但你可以更灵活地控制某个子类型。
密封类可以有若干个子类,要继承密封类,这些子类必须和它定义在同一个文件里。密封类用 sealed 关键字修饰。