本文将伴随大家进入Kotlin语言的正式学习生涯中,希望大家不要半途而废哦!笔者将Kotlin用于Android开发中,因此将从Android开发的视角叙述相关内容,同时将与Java语言有所联系。
1. 继承
在 Kotlin 中所有类都有一个共同的超类Any,这对于没有超类型声明的类是默认超类:
1
| class Example // 从 Any 隐式继承
|
Any有三个方法:equals()、 hashCode() 与 toString()。因此,为所有 Kotlin 类都定义了这些方法。
如需声明一个显式的超类型,需要使用open关键字修饰超类型,派生类头中把超类型放到冒号之后:
1 2 3
| open class Base(p: Int)
class Derived(p: Int) : Base(p)
|
如果派生类有一个主构造函数,其基类必须用派生类主构造函数的参数就地初始化。如果派生类没有主构造函数,那么每个次构造函数必须使用super关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:
1 2 3 4 5 6 7
| class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
|
1.1 覆盖方法
Kotlin 对于可覆盖的成员(我们称之为开放)以及覆盖后的成员需要显式修饰符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| open class Shape {
open fun draw() { }
fun fill() { }
}
class Circle() : Shape() {
override fun draw() { }
}
|
Circle.draw()函数上必须加上override修饰符。如果没写,编译器将会报错。 如果函数没有标注open如Shape.fill(),那么子类中不允许定义相同签名的函数, 不论加不加override。将open修饰符添加到final类(即没有 open的类)的成员上不起作用。
标记为override的成员本身是开放的,也就是说,它可以在子类中覆盖。如果你想禁止再次覆盖,使用final关键字:
1 2 3 4 5 6
| open class Rectangle() : Shape() {
final override fun draw() { }
}
|
1.2 覆盖属性
属性覆盖与方法覆盖类似;在超类中声明并用open修饰,然后在派生类中重新声明的属性必须以override开头,并且它们必须具有兼容的类型。 每个声明的属性可以由具有初始化器的属性或者具有get方法的属性覆盖。
1 2 3 4 5 6 7 8 9 10 11
| open class Shape {
open val vertexCount: Int = 0
}
class Rectangle : Shape() {
override val vertexCount = 4
}
|
你也可以用一个var属性覆盖一个val属性,但反之则不行。 这是允许的,因为一个val属性本质上声明了一个get方法, 而将其覆盖为var只是在子类中额外声明一个set方法。
请注意,你可以在主构造函数中使用override关键字作为属性声明的一部分。
1 2 3 4 5 6 7 8 9 10 11 12 13
| interface Shape {
val vertexCount: Int
}
class Polygon : Shape {
override var vertexCount: Int = 0
}
class Rectangle(override val vertexCount: Int = 4) : Shape // 总是有 4 个顶点
|
1.3 派生类初始化顺序
在构造派生类的新实例的过程中,第一步完成其基类的初始化(在之前只有对基类构造函数参数的求值),因此发生在派生类的初始化逻辑运行之前。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| open class Base(val name: String) {
init { println("Initializing Base") }
open val size: Int = name.length.also { println("Initializing size in Base: $it") }
}
class Derived(name: String,val lastName: String) : Base(name.capitalize().also { println("Argument for Base: $it") }) {
init { println("Initializing Derived") }
override val size: Int =(super.size + lastName.length).also { println("Initializing size in Derived: $it") }
}
|
执行代码Constructing Derived("hello", "world"),输出结果为:
1 2 3 4 5
| Argument for Base: Hello Initializing Base Initializing size in Base: 5 Initializing Derived Initializing size in Derived: 10
|
这意味着,基类构造函数执行时,派生类中声明或覆盖的属性都还没有初始化。如果在基类初始化逻辑中(直接或通过另一个覆盖的open成员的实现间接)使用了任何一个这种属性,那么都可能导致不正确的行为或运行时故障。设计一个基类时,应该避免在构造函数、属性初始化器以及init块中使用open成员。
1.4 调用超类实现
派生类中的代码可以使用super关键字调用其超类的函数与属性访问器的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| open class Rectangle {
open fun draw() { println("Drawing a rectangle") }
val borderColor: String get() = "black"
}
class FilledRectangle : Rectangle() {
override fun draw() { super.draw() println("Filling the rectangle") }
val fillColor: String get() = super.borderColor
}
|
在一个内部类中访问外部类的超类,可以通过由外部类名限定的super关键字来实现:super@Outer:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| class FilledRectangle: Rectangle() {
fun draw() { }
val borderColor: String get() = "black"
inner class Filler {
fun fill() { }
fun drawAndFill() { super@FilledRectangle.draw() fill() println("Drawn a filled rectangle with color ${super@FilledRectangle.borderColor}") }
}
}
|
1.5 覆盖规则
在 Kotlin 中,实现继承由下述规则规定:如果一个类从它的直接超类继承相同成员的多个实现, 它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一)。 为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的super,如super<Base>:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| open class Rectangle {
open fun draw() { }
}
interface Polygon {
fun draw() { }
}
class Square() : Rectangle(), Polygon {
override fun draw() { super<Rectangle>.draw() super<Polygon>.draw() }
}
|
可以同时继承Rectangle与Polygon, 但是二者都有各自的draw()实现,所以我们必须在Square中覆盖draw(), 并提供其自身的实现以消除歧义。
1.6 抽象类
类以及其中的某些成员可以声明为abstract。 抽象成员在本类中可以不用实现。 需要注意的是,我们并不需要用open标注一个抽象类或者函数——因为这不言而喻。
我们可以用一个抽象成员覆盖一个非抽象的开放成员。
1 2 3 4 5 6 7 8 9 10 11
| open class Polygon {
open fun draw() {}
}
abstract class Rectangle : Polygon() {
override abstract fun draw()
}
|
2. 接口
Kotlin 的接口可以既包含抽象方法的声明也包含实现。与抽象类不同的是,接口无法保存状态。它可以有属性但必须声明为抽象或提供访问器实现。
使用关键字interface来定义接口。
1 2 3 4 5 6 7 8 9
| interface MyInterface {
fun bar()
fun foo() { }
}
|
2.1 实现接口
一个类或者对象可以实现一个或多个接口。
1 2 3 4 5 6 7
| class Child : MyInterface {
override fun bar() { }
}
|
接口的实现方式与继承相似,并且Kotlin的接口可以有属性和实现的方法,可以达到类似于多继承的效果。
2.2 接口中的属性
你可以在接口中定义属性。在接口中声明的属性要么是抽象的,要么提供访问器的实现。在接口中声明的属性不能有幕后字段(backing field),因此接口中声明的访问器不能引用它们。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| interface MyInterface {
val prop: Int
val propertyWithImplementation: String get() = "foo"
fun foo() { print(prop) }
}
class Child : MyInterface {
override val prop: Int = 29
}
|
2.3 接口继承
一个接口可以从其他接口派生,从而既提供基类型成员的实现也声明新的函数与属性。很自然地,实现这样接口的类只需定义所缺少的实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| interface Named {
val name: String
}
interface Person : Named {
val firstName: String val lastName: String override val name: String get() = "$firstName $lastName"
}
data class Employee( // 不必实现“name” override val firstName: String, override val lastName: String, val position: Position ) : Person
|
2.4 解决覆盖冲突
实现多个接口时,可能会遇到同一方法继承多个实现的问题。例如
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
| interface A {
fun foo() { print("A") }
fun bar()
}
interface B {
fun foo() { print("B") }
fun bar() { print("bar") }
}
class C : A {
override fun bar() { print("bar") }
}
class D : A, B {
override fun foo() { super<A>.foo() super<B>.foo() }
override fun bar() { super<B>.bar() }
}
|
上例中,接口 A 和 B 都定义了方法foo()和bar()。 两者都实现了foo(), 但是只有 B 实现了bar()(bar() 在 A 中没有标记为抽象, 因为没有方法体时默认为抽象)。因为 C 是一个实现了 A 的具体类,所以必须要重写bar()并实现这个抽象方法。
然而,如果我们从 A 和 B 派生 D,我们需要实现我们从多个接口继承的所有方法,并指明 D 应该如何实现它们。这一规则既适用于继承单个实现(bar())的方法也适用于继承多个实现(foo())的方法。
3. 扩展
Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。 这通过叫做扩展的特殊声明完成。 例如,你可以为一个你不能修改的、来自第三方库中的类编写一个新的函数。 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用。 这种机制称为扩展函数。此外,也有扩展属性, 允许你为一个已经存在的类添加新的属性。
3.1 扩展函数
声明一个扩展函数,我们需要用一个接收者类型也就是被扩展的类型来作为他的前缀。 下面代码为MutableList<Int>添加一个swap函数:
1 2 3 4 5
| fun MutableList<Int>.swap(index1: Int, index2: Int) { val tmp = this[index1] this[index1] = this[index2] this[index2] = tmp }
|
这个this关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象) 现在,我们对任意MutableList<Int>调用该函数了:
1 2
| val list = mutableListOf(1, 2, 3) list.swap(0, 2)
|
当然,这个函数对任何 MutableList 起作用,我们可以泛化它:
1 2 3 4 5
| fun <T> MutableList<T>.swap(index1: Int, index2: Int) { val tmp = this[index1] this[index1] = this[index2] this[index2] = tmp }
|
为了在接收者类型表达式中使用泛型,我们要在函数名前声明泛型参数。
3.2 扩展是静态解析的
扩展不能真正的修改他们所扩展的类。通过定义一个扩展,你并没有在一个类中插入新成员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数。
我们想强调的是扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。例如:
1 2 3 4 5 6 7 8 9 10 11 12 13
| open class Shape
class Rectangle: Shape() {}
fun Shape.getName() = "Shape"
fun Rectangle.getName() = "Rectangle"
fun printClassName(s: Shape) { println(s.getName()) }
printClassName(Rectangle())
|
这个例子会输出 “Shape”,因为调用的扩展函数只取决于参数s的声明类型,该类型是Shape类。
如果一个类定义有一个成员函数与一个扩展函数,而这两个函数又有相同的接收者类型、 相同的名字,并且都适用给定的参数,这种情况总是取成员函数。 例如:
1 2 3 4 5 6 7 8 9
| class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType() { println("Extension function") }
Example().printFunctionType()
|
这段代码输出“Class method”。
当然,扩展函数重载同样名字但不同签名成员函数也完全可以:
1 2 3 4 5 6 7 8 9
| class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType(i: Int) { println("Extension function") }
Example().printFunctionType(1)
|
这段代码输出“Extension function”。
3.3 可空接收者
注意可以为可空的接收者类型定义扩展。这样的扩展可以在对象变量上调用, 即使其值为null,并且可以在函数体内检测this == null,这能让你在没有检测null的时候调用 Kotlin 中的toString():检测发生在扩展函数的内部。
1 2 3 4 5 6
| fun Any?.toString(): String { if (this == null) return "null" return toString() }
|
3.4 扩展属性
与函数类似,Kotlin 支持扩展属性:
1 2
| val <T> List<T>.lastIndex: Int get() = size - 1
|
注意:由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的getters/setters定义。例如:
3.5 伴生对象的扩展
如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数与属性。就像伴生对象的常规成员一样, 可以只使用类名作为限定符来调用伴生对象的扩展成员:
1 2 3 4 5 6 7 8 9
| class MyClass {
companion object { }
}
fun MyClass.Companion.printCompanion() { println("companion") }
fun main() { MyClass.printCompanion() }
|
3.6 扩展的作用域
大多数时候我们在顶层定义扩展——直接在包里:
1 2 3
| package org.example.declarations
fun List<String>.getLongestString() { }
|
要使用所定义包之外的一个扩展,我们需要在调用方导入它:
1 2 3 4 5 6 7 8
| package org.example.usage
import org.example.declarations.getLongestString
fun main() { val list = listOf("red", "green", "blue") list.getLongestString() }
|
3.7 扩展声明为成员
在一个类内部你可以为另一个类声明扩展。在这样的扩展内部,有多个隐式接收者—— 其中的对象成员可以无需通过限定符访问。扩展声明所在的类的实例称为分发接收者,扩展方法调用所在的接收者类型的实例称为扩展接收者。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| class Host(val hostname: String) {
fun printHostname() { print(hostname) }
}
class Connection(val host: Host, val port: Int) {
fun printPort() { print(port) }
fun Host.printConnectionString() { printHostname() print(":") printPort() }
fun connect() { host.printConnectionString() }
}
fun main() { Connection(Host("kotl.in"), 443).connect() }
|
对于分发接收者与扩展接收者的成员名字冲突的情况,扩展接收者优先。要引用分发接收者的成员你可以使用限定的this语法。
1 2 3 4 5 6 7 8
| class Connection {
fun Host.getConnectionString() { toString() this@Connection.toString() }
}
|
声明为成员的扩展可以声明为open并在子类中覆盖。这意味着这些函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37
| open class Base { }
class Derived : Base() { }
open class BaseCaller {
open fun Base.printFunctionInfo() { println("Base extension function in BaseCaller") }
open fun Derived.printFunctionInfo() { println("Derived extension function in BaseCaller") }
fun call(b: Base) { b.printFunctionInfo() }
}
class DerivedCaller: BaseCaller() {
override fun Base.printFunctionInfo() { println("Base extension function in DerivedCaller") }
override fun Derived.printFunctionInfo() { println("Derived extension function in DerivedCaller") }
}
fun main() { BaseCaller().call(Base()) DerivedCaller().call(Base()) DerivedCaller().call(Derived()) }
|
输出结果为:
1 2 3
| Base extension function in BaseCaller Base extension function in DerivedCaller Base extension function in DerivedCaller
|