继承
在 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() 的子类,然后用这个子类构造出对象 p
val p = object : Player(){
override fun load() = "anonymous nothing...."
}
println(p.load())
}
伴生对象
如果你想将某个对象的初始化和一个类实例捆绑在一起,可以考虑使用伴生对象,使用 companion 修饰符,你可以在一个类定义里声明一个伴生对象,一个类里只能有一个伴生对象。
import java.io.File
open 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 关键字修饰。