类与继承
类
Kotlin 中使用关键字 class 声明类
class Invoice { /*......*/ }
类声明由类名、类头(指定其类型参数、主构造函数等)以及由花括号包围的类体构成。类头与类体都是可选的; 如果一个类没有类体,可以省略花括号。
class Empty
构造函数
在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数。主构造函数是类头的一部分:它跟在类名(与可选的类型参数)后。
- 主构造函数
class Person constructor(firstName: String) { /*......*/ }
如果主构造函数没有任何注解或者可⻅性修饰符,可以省略这个 constructor 关键字。
class Person(firstName: String) { /*......*/ }
主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块 (initializer blocks) 中。
在实例初始化期间,初始化块按照它们出现在类体中的顺序执行,与属性初始化器交织在一起:
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
println("First initializer block that prints ${name}")
}
val secondProperty = "Second property: ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
请注意,主构造的参数可以在初始化块中使用。它们也可以在类体内声明的属性初始化器中使用:
class Customer(name: String) {
val customerKey = name.toUpperCase()
}
事实上,声明属性以及从主构造函数初始化属性,Kotlin 有简洁的语法:
class Person(val firstName: String, val lastName: String, var age: Int) { /*......*/ }
与普通属性一样,主构造函数中声明的属性可以是可变的(var)或只读的(val)。
如果构造函数有注解或可⻅性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前
面
class Customer public @Inject constructor(name: String) { /*......*/ }
- 次构造函数
类也可以声明前缀有 constructor的次构造函数:
class Person {
var children: MutableList<Person> = mutableListOf<Person>();
constructor(parent: Person) {
parent.children.add(this)
}
}
请注意,初始化块中的代码实际上会成为主构造函数的一部分。委托给主构造函数会作为次构造函数的第一条语句,因此所有初始化块与属性初始化器中的代码都会在次构造函数体之前执行。即使该类没有主构造函数,这种委托仍会隐式发生,并且仍会执行初始化块:
class Constructors {
init {
println("Init block")
}
constructor(i: Int) {
println("Constructor")
}
}
如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数。构造函数的可⻅性是 public。如果你不希望你的类有一个公有构造函数,你需要声明一个带有非默认可⻅性的空的主构造函数:
class DontCreateMe private constructor () { /*......*/ }
创建类的实例
要创建一个类的实例,我们就像普通函数一样调用构造函数:
val invoice = Invoice()
val customer = Customer("Joe Smith")
注意 Kotlin 并没有 new 关键字。
类成员
类可以包含:
- 构造函数与初始化块
- 函数
- 属性
- 嵌套类与内部类
- 对象声明
继承
在 Kotlin 中所有类都有一个共同的超类 Any ,这对于没有超类型声明的类是默认超类:
与Java不同的是,Java默认的是一个叫object的类
class Example // 从 Any 隐式继承
Any 有三个方法: equals() 、 hashCode() 与 toString() 。因此,为所有 Kotlin 类都定义了这些方法。如需声明一个显式的超类型,请在类头中把超类型放到冒号之后:
open class Base(p: Int)
class Derived(p: Int) : Base(p)
如果派生类有一个主构造函数,其基类可以(并且必须) 用派生类主构造函数的参数就地初始化。如果派生类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点。 注意,在这种情况下,不同的次构造函数可以调用基类型的不同的构造函数:
import android.content.Context
import android.util.AttributeSet
import android.view.View
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
覆盖方法
我们之前提到过,Kotlin 力求清晰显式。因此,Kotlin 对于可覆盖的成员(我们称之为开放)以及覆盖后的成员需要显式修饰符:
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 关键字:
open class Rectangle() : Shape() {
final override fun draw() { /*......*/ }
}
覆盖属性
属性覆盖与方法覆盖类似;在超类中声明然后在派生类中重新声明的属性必须以override 开头,并且它们必须具有兼容的类型。 每个声明的属性可以由具有初始化器的属性或者具有 get 方法的属性覆盖。
open class Shape {
open val vertexCount: Int = 0
}
class Rectangle : Shape() {
override val vertexCount = 4
}
你也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。 这是允许的,因为一个 val 属性本质上声明了一个 get 方法, 而将其覆盖为 var 只是在子类中额外声明一个 set 方法。请注意,你可以在主构造函数中使用 override 关键字作为属性声明的一部分。
interface Shape {
val vertexCount: Int
}
class Rectangle(override val vertexCount: Int = 4) : Shape // 总是有 4 个顶点
class Polygon : Shape {
override var vertexCount: Int = 0
}
派生类初始化顺序
在构造派生类的新实例的过程中,第一步完成其基类的初始化(在之前只有对基类构造函数参数的求值),因此发生在派生类的初始化逻辑运行之前。
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")
}
}
/*
var derived = Derived("test","test2")
result:
System.out: Argument for Base: Test
System.out: Initializing Base
System.out: Initializing size in Base: 4
System.out: Initializing Derived
System.out: Initializing size in Derived: 9
*/
这意味着,基类构造函数执行时,派生类中声明或覆盖的属性都还没有初始化。如果在基类初始化逻辑中(直接或通过另一个覆盖的 open 成员的实现间接)使用了任何一个这种属性,那么都可能导致不正确的行为或运行时故障。设计一个基类时,应该避免在构造函数、属性初始化器以及 init 块中使用 open成员.
调用超类实现
派生类中的代码可以使用 super 关键字调用其超类的函数与属性访问器的实现:
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 :
class FilledRectangle: Rectangle() {
fun draw() { /* ...... */ }
val borderColor: String get() = "black"
inner class Filler {
fun fill() { /* ...... */ }
fun drawAndFill() {
super@FilledRectangle.draw() // 调用 Rectangle 的 draw() 实现
fill()
println("Drawn a filled rectangle with color
${super@FilledRectangle.borderColor}") // 使用 Rectangle 所实现的 borderColor 的 get()
}
}
}
覆盖规则
在 Kotlin 中,实现继承由下述规则规定:如果一个类从它的直接超类继承相同成员的多个实现, 它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一)。 为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super,如 super<Base> :
open class Rectangle {
open fun draw() { /* ...... */ }
}
interface Polygon {
fun draw() { /* ...... */ } // 接口成员默认就是“open”的
}
class Square() : Rectangle(), Polygon {
// 编译器要求覆盖 draw():
override fun draw() {
super<Rectangle>.draw() // 调用 Rectangle.draw()
super<Polygon>.draw() // 调用 Polygon.draw()
}
}
可以同时继承 Rectangle 与 Polygon , 但是二者都有各自的 draw() 实现,所以我们必须在
Square 中覆盖 draw() , 并提供其自身的实现以消除歧义。
抽象类
类以及其中的某些成员可以声明为 abstract。 抽象成员在本类中可以不用实现。 需要注意的是,我们
并不需要用 open 标注一个抽象类或者函数⸺因为这不言而喻。
我们可以用一个抽象成员覆盖一个非抽象的开放成员:
open class Polygon {
open fun draw() {}
}
abstract class Rectangle : Polygon() {
override abstract fun draw()
}
伴生对象
如果你需要写一个可以无需用一个类的实例来调用、但需要访问类内部的函数(例如,工厂方法),你可以把它写成该类内对象声明中的一员。
更具体地讲,如果在你的类内声明了一个伴生对象, 你就可以访问其成员,只是以类名作为限定符。
类内部的对象声明可以用 companion 关键字标记:
class MyClass {
companion object Factory {
fun create(): MyClass = MyClass()
}
}
该伴生对象的成员可通过只使用类名作为限定符来调用:
val instance = MyClass.create()
属性与字段
声明属性
Kotlin 类中的属性既可以用关键字 var 声明为可变的,也可以用关键字 val 声明为只读的。
class Address {
var name: String = "Holmes, Sherlock"
var street: String = "Baker"
var city: String = "London"
var state: String? = null
var zip: String = "123456"
}
要使用一个属性,只要用名称引用它即可:
fun copyAddress(address: Address): Address {
val result = Address() // Kotlin 中没有“new”关键字
result.name = address.name // 将调用访问器
result.street = address.street
// ......
return result
}
Getters 与 Setters
声明一个属性的完整语法是
var <propertyName>[: <PropertyType>] [= <property_initializer>]
[<getter>]
[<setter>]
其初始器(initializer)、getter 和 setter 都是可选的。属性类型如果可以从初始器 (或者从其 getter 返回值,如下文所示)中推断出来,也可以省略。
var allByDefault: Int? // 错误:需要显式初始化器,隐含默认 getter 和 setter
var initialized = 1 // 类型 Int、默认 getter 和 setter
一个只读属性的语法和一个可变的属性的语法有两方面的不同:
- 只读属性的用 val 开始代替 var
- 只读属性不允许 setter
val simple: Int? // 类型 Int、默认 getter、必须在构造函数中初始化
val inferredType = 1 // 类型 Int 、默认 getter
我们可以为属性定义自定义的访问器。如果我们定义了一个自定义的 getter,那么每次访问该属性时都会调用它 (这让我们可以实现计算出的属性)。以下是一个自定义 getter 的示例:
val isEmpty: Boolean
get() = this.size == 0
如果我们定义了一个自定义的 setter,那么每次给属性赋值时都会调用它。一个自定义的 setter 如下所
示:
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 解析字符串并赋值给其他属性
}
按照惯例,setter 参数的名称是 value ,但是如果你喜欢你可以选择一个不同的名称。
自 Kotlin 1.1 起,如果可以从 getter 推断出属性类型,则可以省略它:
val isEmpty get() = this.size == 0 // 具有类型 Boolean
如果你需要改变一个访问器的可⻅性或者对其注解,但是不需要改变默认的实现, 你可以定义访问器而
不定义其实现:
var setterVisibility: String = "abc"
private set // 此 setter 是私有的并且有默认实现
var setterWithAnnotation: Any? = null
@Inject set // 用 Inject 注解此 setter
幕后字段
在 Kotlin 类中不能直接声明字段。然而,当一个属性需要一个幕后字段时,Kotlin 会自动提供。这个幕后字段可以使用 field 标识符在访问器中引用:
var counter = 0 // 注意:这个初始器直接为幕后字段赋值
set(value) {
if (value >= 0) field = value
}
field 标识符只能用在属性的访问器内。
如果属性至少一个访问器使用默认实现,或者自定义访问器通过 field 引用幕后字段,将会为该属性
生成一个幕后字段。
例如,下面的情况下, 就没有幕后字段:
val isEmpty: Boolean
get() = this.size == 0
幕后属性
如果你的需求不符合这套“隐式的幕后字段”方案,那么总可以使用 幕后属性(backing property):
private var _table: Map<String, Int>? = null
public val table: Map<String, Int>
get() {
if (_table == null) {
_table = HashMap() // 类型参数已推断出
}
return _table ?: throw AssertionError("Set to null by another thread")
}
对于 JVM 平台:通过默认 getter 和 setter 访问私有属性会被优化, 所以本例不会引入函数调用开
销。
编译期常量
如果只读属性的值在编译期是已知的,那么可以使用 const 修饰符将其标记为编译期常量。 这种属性
需要满足以下要求:
- 位于顶层或者是 object 声明 或 companion object 的一个成员
- 以 String 或原生类型值初始化
- 没有自定义 getter
这些属性可以用在注解中:
const val SUBSYSTEM_DEPRECATED: String = "This subsystem is deprecated"
@Deprecated(SUBSYSTEM_DEPRECATED) fun foo() { ...... }
延迟初始化属性与变量
一般地,属性声明为非空类型必须在构造函数中初始化。 然而,这经常不方便。例如:属性可以通过依赖注入来初始化, 或者在单元测试的 setup 方法中初始化。 这种情况下,你不能在构造函数内提供一个非空初始器。 但你仍然想在类体中引用该属性时避免空检测。
为处理这种情况,你可以用 lateinit 修饰符标记该属性:
public class MyTest {
lateinit var subject: TestSubject
@SetUp fun setup() {
subject = TestSubject()
}
@Test
fun test() {
subject.method()
}
// 直接解引用
}
该修饰符只能用于在类体中的属性(不是在主构造函数中声明的 var 属性,并且仅当该属性没有自定义 getter 或 setter 时),而自 Kotlin 1.2 起,也用于顶层属性与局部变量。该属性或变量必须为非空类型,并且不能是原生类型。
在初始化前访问一个 lateinit 属性会抛出一个特定异常,该异常明确标识该属性被访问及它没有初
始化的事实。
检测一个 lateinit var 是否已初始化(自 1.2 起)
要检测一个 lateinit var 是否已经初始化过,请在该属性的引用上使用 .isInitialized :
if (foo::bar.isInitialized) {
println(foo.bar)
}
此检测仅对可词法级访问的属性可用,即声明位于同一个类型内、位于其中一个外围类型中或者位于相
同文件的顶层的属性
委托属性
最常⻅的一类属性就是简单地从幕后字段中读取(以及可能的写入)。 另一方面,使用自定义 getter 和setter 可以实现属性的任何行为。 介于两者之间,属性如何工作有一些常⻅的模式。一些例子:惰性值、通过键值从映射读取、访问数据库、访问时通知侦听器等等
接口
Kotlin 的接口可以既包含抽象方法的声明也包含实现。与抽象类不同的是,接口无法保存状态。它可以
有属性但必须声明为抽象或提供访问器实现。
使用关键字 interface 来定义接口:
interface MyInterface {
fun bar()
fun foo() {
// 可选的方法体
}
}
实现接口
一个类或者对象可以实现一个或多个接口。
class Child : MyInterface {
override fun bar() {
// 方法体
}
}
接口中的属性
你可以在接口中定义属性。在接口中声明的属性要么是抽象的,要么提供访问器的实现。在接口中声明
的属性不能有幕后字段(backing field),因此接口中声明的访问器不能引用它们。
interface MyInterface {
val prop: Int // 抽象的
val propertyWithImplementation: String
get() = "foo"
fun foo() {
print(prop)
}
}
class Child : MyInterface {
override val prop: Int = 29
}
接口继承
一个接口可以从其他接口派生,从而既提供基类型成员的实现也声明新的函数与属性。很自然地,实现这样接口的类只需定义所缺少的实现:
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
解决覆盖冲突
实现多个接口时,可能会遇到同一方法继承多个实现的问题。例如:
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())的方法。
可⻅性修饰符
类、对象、接口、构造函数、方法、属性和它们的 setter 都可以有 可⻅性修饰符。(getter 总是与属性有着相同的可⻅性。) 在 Kotlin 中有这四个可⻅性修饰符: private 、 protected 、 internal和public 。如果没有显式指定修饰符的话,默认可⻅性是 public 。
在本⻚可以学到这些修饰符如何应用到不同类型的声明作用域。
包
函数、属性和类、对象和接口可以在顶层声明,即直接在包内:
// 文件名:example.kt
package foo
fun baz() { ...... }
class Bar { ...... }
- 如果你不指定任何可⻅性修饰符,默认为 public ,这意味着你的声明将随处可⻅;
- 如果你声明为 private ,它只会在声明它的文件内可⻅;
- 如果你声明为 internal ,它会在相同模块内随处可⻅;
- protected 不适用于顶层声明。
注意:要使用另一包中可⻅的顶层声明,仍需将其导入进来。例如:
// 文件名:example.kt
package foo
private fun foo() { ...... } // 在 example.kt 内可⻅
public var bar: Int = 5 // 该属性随处可⻅
private set // setter 只在 example.kt 内可⻅
internal val baz = 6 // 相同模块内可⻅
类和接口
对于类内部声明的成员:
- private 意味着只在这个类内部(包含其所有成员)可⻅;
- protected ⸺ 和 private 一样 + 在子类中可⻅。
- internal ⸺ 能⻅到类声明的 本模块内 的任何客戶端都可⻅其 internal 成员;
- public ⸺ 能⻅到类声明的任何客戶端都可⻅其 public 成员
请注意在 Kotlin 中,外部类不能访问内部类的 private 成员。
如果你覆盖一个 protected 成员并且没有显式指定其可⻅性,该成员还会是 protected 可⻅性。例子:
open class Outer {
private val a = 1
protected open val b = 2
internal val c = 3
val d = 4 // 默认 public
protected class Nested {
public val e: Int = 5
}
}
class Subclass : Outer() {
// a 不可⻅
// b、c、d 可⻅
// Nested 和 e 可⻅
override val b = 5
// “b”为 protected
}
class Unrelated(o: Outer) {
// o.a、o.b 不可⻅
// o.c 和 o.d 可⻅(相同模块)
// Outer.Nested 不可⻅,Nested::e 也不可⻅
}
构造函数
要指定一个类的的主构造函数的可⻅性,使用以下语法(注意你需要添加一个显式 constructor 关键
字):
class C private constructor(a: Int) { ...... }
这里的构造函数是私有的。默认情况下,所有构造函数都是 public ,这实际上等于类可⻅的地方它就可⻅(即 一个 internal 类的构造函数只能在相同模块内可⻅).
局部声明
局部变量、函数和类不能有可⻅性修饰符。// 为什么呢?
模块
可⻅性修饰符 internal 意味着该成员只在相同模块内可⻅。更具体地说, 一个模块是编译在一起的一套 Kotlin 文件:
- 一个 IntelliJ IDEA 模块;
- 一个 Maven 项目;
- 一个 Gradle 源集(例外是 test 源集可以访问 main 的 internal 声明);
- 一次 <kotlinc> Ant 任务执行所编译的一套文件
扩展
Kotlin 能够扩展一个类的新功能而无需继承该类或者使用像装饰者这样的设计模式。 这通过叫做 扩展的特殊声明完成。 例如,你可以为一个你不能修改的、来自第三方库中的类编写一个新的函数。 这个新增的函数就像那个原始类本来就有的函数一样,可以用普通的方法调用。 这种机制称为 扩展函数 。此外,也有 扩展属性 , 允许你为一个已经存在的类添加新的属性。
扩展函数
声明一个扩展函数,我们需要用一个 接收者类型 也就是被扩展的类型来作为他的前缀。 下面代码为MutableList<Int> 添加一个 swap 函数:
fun MutableList<Int>.swap(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
Ref : /kotlin-stdlib-1.3.41-sources.jar!/kotlin/Collections.kt
/**
* A generic ordered collection of elements that supports adding and removing elements.
* @param E the type of elements contained in the list. The mutable list is invariant on its element type.
*/
public interface MutableList<E> : List<E>, MutableCollection<E> {
// Modification Operations
/**
* Adds the specified element to the end of this list.
*
* @return `true` because the list is always modified as the result of this operation.
*/
override fun add(element: E): Boolean
override fun remove(element: E): Boolean
// Bulk Modification Operations
/**
* Adds all of the elements of the specified collection to the end of this list.
*
* The elements are appended in the order they appear in the [elements] collection.
*
* @return `true` if the list was changed as the result of the operation.
*/
override fun addAll(elements: Collection<E>): Boolean
/**
* Inserts all of the elements of the specified collection [elements] into this list at the specified [index].
*
* @return `true` if the list was changed as the result of the operation.
*/
public fun addAll(index: Int, elements: Collection<E>): Boolean
override fun removeAll(elements: Collection<E>): Boolean
override fun retainAll(elements: Collection<E>): Boolean
override fun clear(): Unit
// Positional Access Operations
/**
* Replaces the element at the specified position in this list with the specified element.
*
* @return the element previously at the specified position.
*/
public operator fun set(index: Int, element: E): E
/**
* Inserts an element into the list at the specified [index].
*/
public fun add(index: Int, element: E): Unit
/**
* Removes an element at the specified [index] from the list.
*
* @return the element that has been removed.
*/
public fun removeAt(index: Int): E
// List Iterators
override fun listIterator(): MutableListIterator<E>
override fun listIterator(index: Int): MutableListIterator<E>
// View
override fun subList(fromIndex: Int, toIndex: Int): MutableList<E>
}
这个 this 关键字在扩展函数内部对应到接收者对象(传过来的在点符号前的对象) 现在,我们对任意MutableList<Int> 调用该函数了:
val list = mutableListOf(1, 2, 3)
list.swap(0, 2) // “swap()”内部的“this”会保存“list”的值
Ref: /kotlin-stdlib-common-1.3.41-sources.jar!/kotlin/collections/Collections.kt
/**
* Returns a new [MutableList] with the given elements.
* @sample samples.collections.Collections.Lists.mutableList
*/
public fun <T> mutableListOf(vararg elements: T): MutableList<T> =
if (elements.size == 0) ArrayList() else ArrayList(ArrayAsCollection(elements, isVarargs = true))
当然,这个函数对任何 MutableList<T> 起作用,我们可以泛化它:
fun <T> MutableList<T>.swap0(index1: Int, index2: Int) {
val tmp = this[index1] // “this”对应该列表
this[index1] = this[index2]
this[index2] = tmp
}
为了在接收者类型表达式中使用泛型,我们要在函数名前声明泛型参数。 参⻅泛型函数。
扩展是静态解析的
扩展不能真正的修改他们所扩展的类。通过定义一个扩展,你并没有在一个类中插入新成员, 仅仅是可以通过该类型的变量用点表达式去调用这个新函数。
我们想强调的是扩展函数是静态分发的,即他们不是根据接收者类型的虚方法。 这意味着调用的扩展函数是由函数调用所在的表达式的类型来决定的, 而不是由表达式运行时求值结果决定的。例如:
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 类。如果一个类定义有一个成员函数与一个扩展函数,而这两个函数又有相同的接收者类型、 相同的名字,并且都适用给定的参数,这种情况总是取成员函数。 例如:
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType() { println("Extension function") }
Example().printFunctionType()
这段代码输出“Class method”。
当然,扩展函数重载同样名字但不同签名成员函数也完全可以:
class Example {
fun printFunctionType() { println("Class method") }
}
fun Example.printFunctionType(i: Int) { println("Extension function") }
Example().printFunctionType(1)
可空接收者
注意可以为可空的接收者类型定义扩展。这样的扩展可以在对象变量上调用, 即使其值为 null,并且可以在函数体内检测 this == null ,这能让你在没有检测 null 的时候调用 Kotlin 中的toString():检测发生在扩展函数的内部。
fun Any?.toString(): String {
if (this == null) return "null"
// 空检测之后,“this”会自动转换为非空类型,所以下面的 toString()
// 解析为 Any 类的成员函数
return toString()
}
扩展属性
与函数类似,Kotlin 支持扩展属性:
val <T> List<T>.lastIndex: Int
get() = size - 1
注意:由于扩展没有实际的将成员插入类中,因此对扩展属性来说幕后字段是无效的。这就是为什么扩展属性不能有初始化器。他们的行为只能由显式提供的 getters/setters 定义。例如:
val House.number = 1 // 错误:扩展属性不能有初始化器
伴生对象的扩展
如果一个类定义有一个伴生对象 ,你也可以为伴生对象定义扩展函数与属性。就像伴生对象的常规成员一样, 可以只使用类名作为限定符来调用伴生对象的扩展成员:
class MyClass {
companion object { }
}
// 将被称为 "Companion"
fun MyClass.Companion.printCompanion() { println("companion") }
fun main() {
MyClass.printCompanion()
}
扩展的作用域
大多数时候我们在顶层定义扩展⸺直接在包里:
package org.example.declarations
fun List<String>.getLongestString() { /*......*/}
要使用所定义包之外的一个扩展,我们需要在调用方导入它:
package org.example.usage
import org.example.declarations.getLongestString
fun main() {
val list = listOf("red", "green", "blue")
list.getLongestString()
}
扩展声明为成员
在一个类内部你可以为另一个类声明扩展。在这样的扩展内部,有多个 隐式接收者 ⸺ 其中的对象成员
可以无需通过限定符访问。扩展声明所在的类的实例称为分发接收者,扩展方法调用所在的接收者类型
的实例称为扩展接收者 。
class Connection(val host: Host, val port: Int) {
fun printPort() { print(port) }
fun Host.printConnectionString() {
printHostname()
// 调用 Host.printHostname()
print(":")
printPort()
// 调用 Connection.printPort()
}
fun connect() {
/*......*/
host.printConnectionString()
}
// 调用扩展函数
}
fun test() {
Connection(Host("kotl.in"), 443).connect()
//Host("kotl.in").printConnectionString(443)
}
对于分发接收者与扩展接收者的成员名字冲突的情况,扩展接收者优先。要引用分发接收者的成员你可以使用 限定的 this 语法。
class Connection {
fun Host.getConnectionString() {
toString()
// 调用 Host.toString()
this@Connection.toString() // 调用 Connection.toString()
}
}
声明为成员的扩展可以声明为 open 并在子类中覆盖。这意味着这些函数的分发对于分发接收者类型是虚拟的,但对于扩展接收者类型是静态的。
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())
// “Base extension function in BaseCaller”
DerivedCaller().call(Base()) // “Base extension function in DerivedCaller”——分发接收者虚拟解析
DerivedCaller().call(Derived()) // “Base extension function in DerivedCaller”——扩展接收者静态解析
}
关于可⻅性的说明
扩展的可⻅性与相同作用域内声明的其他实体的可⻅性相同。例如:
- 在文件顶层声明的扩展可以访问同一文件中的其他 private 顶层声明;
- 如果扩展是在其接收者类型外部声明的,那么该扩展不能访问接收者的 private 成员。
数据类
我们经常创建一些只保存数据的类。 在这些类中,一些标准函数往往是从数据机械推导而来的。在Kotlin 中,这叫做 数据类 并标记为 data :
data class User(val name: String, val age: Int)
编译器自动从主构造函数中声明的所有属性导出以下成员:
- equals() / hashCode() 对;
- toString() 格式是 "User(name=John, age=42)" ;
- componentN() 函数 按声明顺序对应于所有属性;
- copy() 函数(⻅下文)
为了确保生成的代码的一致性以及有意义的行为,数据类必须满足以下要求: - 主构造函数需要至少有一个参数;
- 主构造函数的所有参数需要标记为 val 或 var ;
- 数据类不能是抽象、开放、密封或者内部的;
- (在1.1之前)数据类只能实现接口。
此外,成员生成遵循关于成员继承的这些规则: - 如果在数据类体中有显式实现 equals() 、 hashCode() 或者 toString() ,或者这些函数在父类中有 final 实现,那么不会生成这些函数,而会使用现有函数;
- 如果超类型具有 open 的 componentN() 函数并且返回兼容的类型, 那么会为数据类生成相应的函数,并覆盖超类的实现。如果超类型的这些函数由于签名不兼容或者是 final 而导致无法覆盖,那么会报错;
- 从一个已具 copy(......) 函数且签名匹配的类型派生一个数据类在 Kotlin 1.2 中已弃用,并且在Kotlin 1.3 中已禁用。
- 不允许为 componentN() 以及 copy() 函数提供显式实现。
自 1.1 起,数据类可以扩展其他类(示例请参⻅密封类)。
在 JVM 中,如果生成的类需要含有一个无参的构造函数,则所有的属性必须指定默认值。(参⻅构造函数)。
data class User(val name: String = "", val age: Int = 0)
在类体中声明的属性
请注意,对于那些自动生成的函数,编译器只使用在主构造函数内部定义的属性。如需在生成的实现中
排除一个属性,请将其声明在类体中:
data class Person(val name: String) {
var age: Int = 0
}
在 toString() 、 equals() 、 hashCode() 以及 copy() 的实现中只会用到 name 属性,并且只有一个 component 函数 component1() 。虽然两个 Person 对象可以有不同的年龄,但它们会视为相等。
val person1 = Person("John")
val person2 = Person("John")
person1.age = 10
person2.age = 20
复制
在很多情况下,我们需要复制一个对象改变它的一些属性,但其余部分保持不变。 copy() 函数就是为此而生成。对于上文的 User 类,其实现会类似下面这样:
fun copy(name: String = this.name, age: Int = this.age) = User(name, age)
这让我们可以写:
val jack = User(name = "Jack", age = 1)
val olderJack = jack.copy(age = 2)
数据类与解构声明
为数据类生成的 Component 函数 使它们可在解构声明中使用:
val jane = User("Jane", 35)
val (name, age) = jane
println("$name, $age years of age") // 输出 "Jane, 35 years of age"
标准数据类
标准库提供了 Pair 与 Triple 。尽管在很多情况下具名数据类是更好的设计选择, 因为它们通过为属性提供有意义的名称使代码更具可读性。
密封类
密封类用来表示受限的类继承结构:当一个值为有限几种的类型、而不能有任何其他类型时。在某种意义上,他们是枚举类的扩展:枚举类型的值集合也是受限的,但每个枚举常量只存在一个实例,而密封类的一个子类可以有可包含状态的多个实例。
要声明一个密封类,需要在类名前面添加 sealed 修饰符。虽然密封类也可以有子类,但是所有子类都必须在与密封类自身相同的文件中声明。(在 Kotlin 1.1 之前, 该规则更加严格:子类必须嵌套在密封类声明的内部)。
sealed class Expr
data class Const(val number: Double) : Expr()
data class Sum(val e1: Expr, val e2: Expr) : Expr()
object NotANumber : Expr()
(上文示例使用了 Kotlin 1.1 的一个额外的新功能:数据类扩展包括密封类在内的其他类的可能性。 )
一个密封类是自身抽象的,它不能直接实例化并可以有抽象(abstract)成员。
密封类不允许有非-private 构造函数(其构造函数默认为 private)。
请注意,扩展密封类子类的类(间接继承者)可以放在任何位置,而无需在同一个文件中。使用密封类的关键好处在于使用 when 表达式 的时候,如果能够验证语句覆盖了所有情况,就不需要为该语句再添加一个 else 子句了。当然,这只有当你用 when 作为表达式(使用结果)而不是作为语句时才有用。
fun eval(expr: Expr): Double = when(expr) {
is Const -> expr.number
is Sum -> eval(expr.e1) + eval(expr.e2)
NotANumber -> Double.NaN
// 不再需要 `else` 子句,因为我们已经覆盖了所有的情况
}
网友评论