1. 继承与构造函数
1.1 继承 :
在Kotlin中,任何一个非抽象类默认都是不可以被继承的,相当于JAVA中给类声明了final关键字
抽象类本身无法创建实例,一定要由子类去继承它才能创建实例,因此抽象类必须可以被继承才行
之所以这么设计,应该是与var关键字差不多,最好都是不可变的,因为一个类允许被继承的话,无法预知子类会存在什么风险
在Effective JAVA一书中提到,如果一个类不是专门为继承而设计,就一个为这个类加上final声明
若要使一个类可以被继承,只需在该类前加上open关键字即可,子类继承父类由JAVA中的extends关键字转变为 :
// 父类Person
open class Person {
var age: Int? = 0
var name: String? = ""
fun say() {
println("age = $age, name = $name")
}
}
// 子类Student
class Student : Person() {
}
为什么子类继承父类后,Person类的后面需要加上一个括号?因为在Kotlin中,还会涉及到主构造函数、次构造函数等方面
1.2 主构造函数
在每个类中默认都会有一个不带参数的主构造函数,主构造函数的特点是没有函数体,直接定义在类名后面即可
在Kotlin中,我们可以将相关的字段放在主构造函数中,例如:
// 将新增的两个字段 height 与 school 放在主构造函数中
class Student(val height: Int, val school: String) : Person() {
// 主构造函数没有函数体,若要编写其中的逻辑,则可以在init结构体中编写
init {
}
}
为什么子类继承父类时,需要带上括号呢?因为继承特性,子类继承父类时,子类的构造函数必须调用父类的构造函数,在Kotlin里,子类的主构造函数调用父类的哪个构造函数,在继承时通过括号来指定
// 父类Person
open class Person(var age: Int, var name: String) {
constructor() : this(0, "") {
// 无参构造函数
}
fun say() {
println("age = $age, name = $name")
}
}
// 将子类Student
class Student(val height: Int, val school: String, age: Int, name: String) : Person(age, name) {
}
子类指定调用父类的带参构造函数后,子类主构造函数中的age、name字段,不能再将它们声明为val或者var,因为在主构造函数中会自动成为该类的字段,就会导致与父类中同名的age、name冲突,因此在该参数前不加任何关键字,让它作用域仅限定在主构造函数中即可
1.3 次构造函数
每个类中只能有一个主构造函数,但是可以有多个次构造函数
注意:Kotlin规定,当一个类既有主构造函数又有次构造函数时,所有的次构造函数都必须调用主构造函数(包括间接调用)
class Student(val height: Int, val school: String, age: Int, name: String)
: Person(age, name) {
constructor(age: Int, name: String) : this(0, "", age, name) {
}
constructor() : this(0, "") {
}
}
次构造函数是通过constructor关键字来定义的,以上面的例子为例:
第一个次构造函数接收age和name两个参数,然后又通过this调用其主构造函数,并将height与school设定默认值;
第二个次构造函数不接受任何参数,通过this关键字调用之前定义的第一个次构造函数,并将age和name设定默认值,因此相当于间接调用了主构造函数;
有一种特殊的场景,当类中仅有次构造函数,没有主构造函数的时候,此时继承父类时,不需要加上括号:
class Child : Person {
constructor(age: Int, name: String) : super(age, name) {
}
}
由于没有主构造函数,次构造函数只能直接调用父类的构造函数,只需将this转换成super关键字,跟JAVA很相似
2. 接口 interface
Kotlin中的接口部分与Java中的几乎一致,接口用于实现多态编程的重要组成部分
interface Study {
fun read()
fun write()
}
// Student类实现Study接口
class Student : Study {
override fun read() {
}
override fun write() {
}
}
在Kotlin中,为了让接口更加灵活,还增加了一个额外的功能:允许对接口中定义的函数进行默认实现。这个功能在Java 1.8之后也支持
interface Study {
fun read()
fun write()
// 函数默认实现,不需强制被实现
fun sleep() {
}
}
// Student类实现Study接口
class Student : Study {
override fun read() {
}
override fun write() {
}
}
3. Kotlin关键字
private:作用与Java中的一致,只对当前类内部可见
public:与Java一致,对所有类可见,在Kotlin中public是默认项,而在Java中default是默认项
protected:在Kotlin中仅对当前类与子类可见
internal:在Kotlin中,抛弃了default关键字,引入新的可见性概念internal,只对同一模块中的类可见,常用于模块化编程中
4. 数据类 data class
在Java中,定义一个数据类,通常需要重写equals()、hashCode()、toString()这几个方法。
而在Kotlin中,新增了一个关键字data,当在一个类前面声明了data关键字时,就表明你定义这个类为数据类,Kotlin会根据主构造函数中的参数将equals()、hashCode()、toString()等方法自动生成
data class Student2(val name: String, val age: Int)
5. 单例类 object class
在项目,最常用到的设计模式便是单例模式,通常在Java中定义个单例类的时候,需要编写很多行代码才能实现,而在Kotlin中,为我们提供了单例类
在Kotlin中创建一个单例类极其简单,仅需在定义类的时候,将class关键字改写成object关键字即可
// 单例类
object Student3 {
fun eat() {
}
}
class Main {
// 调用单例类Student3中的eat()方法
fun main() {
Student3.eat()
}
}
调用单例类中方法的方式,就比较类似于调用Java中的静态方法
6. 密封类 sealed class
在使用密封类前,我们用when语句来做判断时,我们不得不再编写一个else条件,否则,Kotlin编译器会认为这里缺少条件分支,无法编译通过。但当我们编写了else条件,下次新增一个新的条件时,忘记在when语句中添加这个条件,这时执行when语句的时候,就会跑到else条件中去,这并不是我们想要的结果
因此,在Kotlin中,为我们新增了密封类sealed class,很好地解决了这个问题。当我们在when语句中传入一个密封类变量作为条件时,Kotlin编译器会自动检查该密封类有哪些子类,并强制要求对这些子类进行条件处理
注意:密封类及其所有子类只能定义在同一个文件的顶层位置,不能嵌套在其他类中,这是被密封类底层的实现机制所限制的
// SealedClass.kt 顶层文件中定义
sealed class Result
class Success(): Result()
class Failure(): Result()
// 普通class文件中函数实现
fun getResult(result: Result) = when(result) {
is Success -> {
println("Success")
}
is Failure -> {
println("Failure")
}
}