1、定义类继承结构
1.1 kotlin中的接口
声明接口
interface Clickable{
fun click()
}
声明了一个只有一个抽象方法的接口,和java中一样,实现这个接口的类要提供这个抽象方法的具体实现。
实现接口
class Button:Clickable{
override fun click()=println("I was clicked")//override修饰符是强制要求的
}
在kotlin中“:”相当于java中的extends或implements。
在接口中定义一个带方法体的方法
interface Clickable{
fun click()
fun showOff()=println("I'm clickable!")//带默认实现的方法
}
我们实现这个接口的类中要为click()方法提供实现。而对于showOff()方法则有些无所谓了,你可以直接调用,也可以重新定义。
1.2 open、final和abstract修饰符:默认为final
kotlin中类和方法默认都是final的,如果想允许一个类有子类,那么需要用open修饰。
open class RichButton:Clickable{//这个类是open的:其他类可以继承它
fun disable(){}//这个函数是默认是final修饰的,虽然没有写出来。不能再子类中重写它
open fun animate(){}//用open修饰,可以再子类中重写
override fun click(){}//重写了一个函数
}
上面的这个click(){}函数是重写的,它默认是open,可以被RichButton的子类再次重写,如果我们不想让它重写了,那么可以用final修饰它。
open class RichButton:Clickable{
final override fun click(){}//这样子类就不能重写了
}
抽象类
kotlin中同样有抽象类,也是用abstract关键字,这种类不能被实例化。抽象类的成员始终是open的,我们不需要明确用open修饰。
abstract class Animated{
abstract fun animate()//抽象函数,子类必须重写它
open fun stopAnimating(){}
fun animateTwice(){}//open修饰符可写可不写
}
修饰符 | 相关成员 | 备注 |
---|---|---|
final | 不能被重写 | 在类中被默认使用 |
open | 可以被重写 | 必须要标明 |
abstract | 必须被重写 | 只能在抽象类中使用,抽象成员不能有实现 |
override | 重写父类或接口中的成员 | 如果没有使用final标明,重写的成员默认是open的 |
1.3 可见性修饰符:默认为public
修饰符 | 类成员 | 顶层声明 |
---|---|---|
public | 所有地方可见 | 所有地方可见 |
internal | 模块中可见 | 模块中可见 |
protected | 子类中可见 | - |
private | 类中可见 | 文件中可见 |
- kotlin中protected成员只在类和它的子类中可见,不可以从同一包内访问一个protected成员。
- 外部类不能看到内部类中private中的成员。
1.4 内部类和嵌套类:默认是嵌套类
类A在另一个类B中声明 | 在java中 | 在kotlin中 |
---|---|---|
嵌套类(不存储外部类的引用) | static class A | class A |
内部类(存储外部类的引用) | class A | inner class A |
1.5 密封类:定义受限的类继承结构
密封类用来表示受限的类继承结构:当一个值为有限几种的类型, 而不能有任何其他类型时。在某种意义上,他们是枚举
类的扩展:枚举类型的值集合 也是受限的,但每个枚举常量只存在一个实例,而密封类 的一个子类可以有可包含状态的多个实例。
声明一个密封类,使用 sealed 修饰类,密封类可以有子类,但是所有的子类都必须要内嵌在密封类中。
使用密封类的关键好处在于使用 when 表达式 的时候,如果能够 验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了。
fun eval(expr: Expr): Double = when(expr) {
is Expr.Const -> expr.number
is Expr.Sum -> eval(expr.e1) + eval(expr.e2)
Expr.NotANumber -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}
2、声明一个带非默认构造方法或属性的类
2.1 初始化类:主构造方法和初始化语句块
我们先声明一个简单类
class User(val nickname:String)
括号里面的语句块叫作主构造方法。
它又两个目的:①标明构造方法的参数②定义使用这些参数初始化的属性
它相当于
class User constructor(nickname:String){//带一个参数的主构造方法
val nickname:String
init{//初始化语句块
this.nickname=nickname
}
}
如果没有给类声明任何构造方法,将会生成一个不带参数的默认构造方法。
2.2 构造方法:用不同的方式来初始化父类
kotlin中的重载
open class View{
constructor(ctx:Context){
}
constructor(ctx:Context,attr:AttributeSet){
}
}
这个类没有声明一个主构造方法,它声明的是两个从构造方法。
2.3 实现在接口中声明的属性
java中不可以声明抽象的成员变量,在kotlin中可以。
interface User{
val nickname:String
}
class PrivateUser(override val nickname:String):User
除了抽象属性声明外,接口还可以包含具有getter和setter属性
2.4 通过getter或setter访问支持字段
实现一个既可以存储值又可以在值被访问和修改时提供额外逻辑的属性。
class User(val name:String){
var address:String="unspecified"
set(value:String){
println("""
Address was changed for $name:
"$field"->"$value".""".trimIndent())//读取支持字段的值
field=value//更新支持字段的值
}
}
2.5 修改访问器的可见性
声明一个具有private setter的属性
class LengthCounter{
val counter:Int=0
private set
fun addWord(word:String){
counter+=word.length
}
}
3、编译器生成的方法:数据类和类委托
3.1 通用对象方法
toString()
equals()
hashCode()
3.2 数据类
Kotlin 可以创建一个只包含数据的类,关键字为 data:
data class User(val name: String, val age: Int)
编译器会自动的从主构造函数中根据所有声明的属性提取以下函数:
- equals() / hashCode()
- toString() 格式如 "User(name=John, age=42)"
- componentN() functions 对应于属性,按声明顺序排列
- copy() 函数
如果这些函数在类中已经被明确定义了,或者从超类中继承而来,就不再会生成。
为了保证生成代码的一致性以及有意义,数据类需要满足以下条件:
- 主构造函数至少包含一个参数。
- 所有的主构造函数的参数必须标识为val 或者 var ;
- 数据类不可以声明为 abstract, open, sealed 或者 inner;
- 数据类不能继承其他类 (但是可以实现接口)。
复制使用 copy() 函数,我们可以使用该函数复制对象并修改部分属性, 对于上文的 User 类,其实现会类似下面这样:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
使用 copy 类复制 User 数据类,并修改 age 属性:
data class User(val name: String, val age: Int)
fun main(args: Array<String>) {
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
println(jack)
println(olderJack)
}
3.3 类委托:使用"by"关键字
委托模式是软件设计模式中的一项基本技巧。在委托模式中,有两个对象参与处理同一个请求,接受请求的对象将请求委托给另一个对象来处理。
类的委托即一个类中定义的方法实际是调用另一个类的对象的方法来实现的。
以下实例中派生类 Derived 继承了接口 Base 所有方法,并且委托一个传入的 Base 类的对象来执行这些方法。
// 创建接口
interface Base {
fun print()
}
// 实现此接口的被委托的类
class BaseImpl(val x: Int) : Base {
override fun print() { print(x) }
}
// 通过关键字 by 建立委托类
class Derived(b: Base) : Base by b
fun main(args: Array<String>) {
val b = BaseImpl(10)
Derived(b).print() // 输出 10
}
在 Derived 声明中,by 子句表示,将 b 保存在 Derived 的对象实例内部,而且编译器将会生成继承自 Base 接口的所有方法, 并将调用转发给 b
Kotlin 直接支持委托模式,更加优雅,简洁。Kotlin 通过关键字 by 实现委托
4、"object"关键字:将声明一个类与创建一个实例结合起来
4.1 对象声明
kotlin中使用object关键字来声明一个对象
通过对象声明获取一个单例很方便
object DataProviderManager {
fun registerDataProvider(provider: DataProvider) {
// ……
}
val allDataProviders: Collection<DataProvider>
get() = // ……
}
当对象声明在另一个类的内部时,这个对象并不能通过外部类的实例访问到该对象,而只能通过类名来访问,同样该对象也不能直接访问到外部类的方法和变量。
4.2 伴生对象
类内部的对象声明可以用 companion 关键字标记,这样它就与外部类关联在一起,我们就可以直接通过外部类访问到对象的内部元素。
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create() // 访问到对象的内部元素
我们可以省略掉该对象的对象名,然后使用 Companion 替代需要声明的对象名。
一个类里面只能声明一个内部关联对象,即关键字 companion 只能使用一次。
4.3 对象表达式
通过对象表达式实现一个匿名内部类的对象用于方法的参数中:
window.addMouseListener(object : MouseAdapter() {
override fun mouseClicked(e: MouseEvent) {
// ...
}
override fun mouseEntered(e: MouseEvent) {
// ...
}
})
与java匿名内部类只能扩展一个类或实现一个接口不同,kotlin的匿名对象可以实现多个接口或者不实现接口。