第4章 类对象和接口
4.1 定义类继承结构
4.1.1 Kotlin中的接口
| //接口定义
interface Clickable {
fun click()
}
//实现接口
class Button:Clickable{
override fun click() = println("I was clicked")
}
|
接口的方法可以有一个默认实现。
| interface Clickable {
fun click()
fun showOff() = println("I'm clickable!") //带默认实现的方法
}
|
假设存在同样定义了一个showOff方法并且有如下实现的另一个接口。
| interface Focusable {
fun setFocus(b: Boolean) =
println("I ${if (b) "got" else "lost"} focus.")
fun showOff() = println("I'm focusable!")
}
|
类实现这两个接口,如果没有显式实现showOff,将会得到如下编译错误:
| class Button : Clickable,Focusable {
override fun click() = println("I was clicked")
override fun showOff() {
super<Clickable>.showOff()
super<Focusable>.showOff()
}
}
|
4.1.2 open、final和abstarc修饰符:默认为final
Java的类和方法默认是open的,而kotlin中默认都是final的。如果你想允许创建一个类的子类, 需要使用open修饰符来标示这个类。此外,需要给每一个可以被重写的属性或方法添加open修饰符。
| open class RichButton:Clickable{
fun disable(){} //final函数
open fun animate(){} //open函数
override fun click() {} //这个函数重写了一个open函数它本身同样是open函数
}
|
如果重写了一个基类或者接口的成员,重写了的成员同样默认是open的,如果你想改变这一行为,阻止你的子类重写你的实现,可以显式地将重写的成员标注为final。
| open class RichButton:Clickable{
final override fun click() {}
}
|
**抽象成员**始终是open的,所以不需要显式地使用open修饰符。
| abstract class Animated{
abstract fun animate()
//抽象类中的非抽象函数并不是默认open的,但是可以标注为open的
open fun stopAnimating(){}
fun animateTwice(){}
}
|
4.1.3 可见性修饰符:默认为public
4.1.4 内部类和嵌套类:默认是嵌套类
与Java不同,Kotlin的嵌套类不能访问外部类的实例。
4.1.5 密封类:定义受限的类继承结构
| interface Expr
class Num(val value: Int) : Expr
class Sum(val left: Expr, val right: Expr) : Expr
fun eval(e: Expr): Int =
when (e) {
is Num -> e.value
is Sum -> eval(e.right) + eval(e.left)
else -> //必须检查else分支
throw IllegalArgumentException("Unknown expression")
}
|
总是不得不添加一个默认分支很不方便。更重要的是,如果你添加一个新的子类,编译器并不能发现有地方改变了,如果你忘记了添加一个新分支,就会选择默认的选项,这可能导致潜在的bug。
Kotlin为这个问题提供了一个解决方案:sealed类。为父类添加一个sealed修饰符,对可能创建的子类做出严格的限制。所有的直接子类必须嵌套在父类中。
| sealed class Expr {
class Num(val value: Int) : Expr()
class Sum(val left: Expr, val right: Expr) : Expr()
}
fun eval(e: Expr): Int =
when (e) { //不需要提供默认分支
is Expr.Num -> e.value
is Expr.Sum -> eval(e.right) + eval(e.left)
}
|
sealed修饰符隐含的这个类是一个open类,不再需要显式地添加open修饰符。
4.2 声明一个带非默认构造方法或属性的类
4.2.1 初始化类:主构造方法和初始化语句块
| class User(val nickname: String)
|
这段被括号围起来的语句块就叫作主构造方法,它主要有两个目的:表明构造方法的参数,以及定义使用这些参数初始化的属性。
| class User constructor(_nickname: String) {
val nickname :String
init {
nickname = _nickname
}
}
|
constructor
关键字用来开始一个主构造方法或从构造方法的声明。init
关键字用来引入一个初始化语句块。因为主构造方法有语法限制,不能包含初始化代码,所以要使用初始化语句块。一个类中可以声明多个初始化语句块。
构造方法参数_nickname
中的下划线用来区分属性的名字和构造方法参数的名字。另一个可选方案是使用同样的名字,通过this来消除歧义。
| class User constructor(nickname: String) {
val nickname :String
init {
this.nickname = nickname
}
}
|
在这个例子中,不需要把初始化代码块放在初始化语句块中,因为它可以与nickname属性的声明结合。如果主构造方法没有注解或可见性修饰符,同样可以去掉constructor关键字。
| class User(_nickname: String) {
val nickname = _nickname
}
|
| class User(val nickname: String,
val isSubscribed:Boolean = true)
|
| open class User(val nickname: String)
class TwitterUser(nickname: String) : User(nickname)
open class Button //将生成一个不带任何参数的默认构造方法
class RadioButton : Button() //注意与接口的区别,接口不带括号
|
如果不想类被其他代码实例化,必须把构造方法标记为private
。
| class Secretive private constructor() {}
|