本文已参与[新人创作礼]活动,一起开启掘金创作之路
持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第8天,点击查看活动详情
类与继承
在 Kotlin 中的一个类可以有一个主构造函数以及一个或多个次构造函数。 如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。
class Person(
val firstName: String,
val lastName: String = "默认值",
var age: Int, // trailing comma
) { }
如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面: 在 Kotlin 中有这四个可见性修饰符:private、 protected、 internal 和 public。 如果没有显式指定修饰符的话,默认可见性是 public。
class Customer public @Inject constructor(name: String) { /*……*/ }
如果类有一个主构造函数,每个次构造函数需要委托给主构造函数, 可以直接委托或者通过别的次构造函数间接委托。委托到同一个类的另一个构造函数用 this 关键字即可:
class Person(val name: String) {
var children: MutableList<Person> = mutableListOf()
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
所有初始化块与属性初始化器中的代码都会在次构造函数体之前执行。即使该类没有主构造函数
class Constructors {
init {
println("Init block")
}
constructor(i: Int) {
println("Constructor")
}
}
打印:
Init block
Constructor
如果一个非抽象类没有声明任何(主或次)构造函数,默认生成不带参数的主构造函数。构造函数的可见性是 public。 如果你不希望你的类有一个公有构造函数,你需要声明一个带有非默认可见性private的空的主构造函数:
class DontCreateMe private constructor () { /*……*/ }
创建类的实例:
//Kotlin 并没有 new 关键字。
val invoice = Invoice()
继承 在 Kotlin 中所有类都有一个共同的超类 Any
class Example // 从 Any 隐式继承
Any 有三个方法:equals()、 hashCode() 与 toString()。因此,为所有 Kotlin 类都定义了这些方法。
Kotlin 类默认是(final)的,不能被继承。 要使一个类可继承,请用 open 关键字
open class Base // 该类开放继承
//用 冒号 :来代替extends
class Derived(p: Int) : Base(p)
如果派生类有一个主构造函数,其基类可以(并且必须) 用派生类主构造函数的参数就地初始化。
如果派生类没有主构造函数,那么每个次构造函数必须使用 super 关键字初始化其基类型,或委托给另一个构造函数做到这一点
派生类即子类
//MyView 继承View,MyView 为派生类
class MyView : View {
constructor(ctx: Context) : super(ctx)
constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}
可覆盖的成员以及覆盖后的成员需要显式修饰符open和override:
open class Shape {
open fun draw() { /*……*/ }
//没标注open,子类中不允许定义相同签名的函数
fun fill() { /*……*/ }
}
class Circle() : Shape() {
//标记为 override 是开放的,它可以在子类中被覆盖。
//如果你想禁止再次覆盖,使用 final 关键字:
final override fun draw() { /*……*/ }
}
你也可以用一个 var 属性覆盖一个 val 属性,但反之则不行。 因为 val 只声明了 get 方法, 而将其覆盖为 var 只是在子类中额外声明一个 set 方法。
interface Shape {
val vertexCount: Int
}
class Rectangle(override val vertexCount: Int = 4) : Shape
class Polygon : Shape {
override var vertexCount: Int = 0
}
基类 = 父类 派生类 = 子类
派生类初始化顺序 在构造派生类的新实例的过程中,第一步完成其基类的初始化(在之前只有对基类构造函数参数的求值),因此发生在派生类的初始化逻辑运行之前。
基类构造函数执行时,派生类中声明或覆盖的属性都还没有初始化。如果在基类初始化逻辑中(直接或通过另一个覆盖的 open 成员的实现间接)使用了任何一个这种属性,那么都可能导致不正确的行为或运行时故障。设计一个基类时,应该避免在构造函数、属性初始化器以及 init 块中使用 open 成员。
设计一个基类时,应该避免在构造函数、属性初始化器以及 init 块中使用 open 成员。
子类可以使用 super 关键字调用父类的函数与属性 内部类中访问外部类的超类,可以通过由外部类名限定的 super 关键字来实现:super@Outer:
如果一个类从它的直接超类继承相同成员的多个实现, 它必须覆盖这个成员并提供其自己的实现(也许用继承来的其中之一)。 为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super,如 super:
open class Rectangle {
open fun draw() { /* …… */ }
}
interface Polygon {
fun draw() { /* …… */ } // 接口成员默认就是“open”的
}
class Square() : Rectangle(), Polygon {
// 编译器要求覆盖 draw():
override fun draw() {
//为了表示采用从哪个超类型继承的实现,我们使用由尖括号中超类型名限定的 super
super<Rectangle>.draw() // 调用 Rectangle.draw()
super<Polygon>.draw() // 调用 Polygon.draw()
}
inner class Filler {
fun test1(){
//内部类中访问外部类的超类,使用super@Outer
super@Square.play()
}
}
}
可以同时继承 Rectangle 与 Polygon, 但是二者都有各自的 draw() 实现,所以我们必须在 Square 中覆盖 draw(), 并提供其自身的实现以消除歧义。
我们可以用一个抽象成员覆盖一个非抽象的开放成员
open class Polygon {
open fun draw() {}
}
abstract class Rectangle : Polygon() {
abstract override fun draw()
}
属性与字段
Kotlin 类中的属性既可以用关键字 var 声明为可变的,也可以用关键字 val 声明为只读的。 只读属性的用 val开始代替var ,只读属性不允许 setter
- 如果自定义 getter,那么每次访问该属性时都会调用它
val isEmpty: Boolean
get() = this.size == 0
- 如果自定义setter,那么每次给属性赋值时都会调用它
var stringRepresentation: String
get() = this.toString()
set(value) {
setDataFromString(value) // 解析字符串并赋值给其他属性
}
如果你需要改变一个访问器的可见性或者对其注解,但是不需要改变默认的实现, 你可以定义访问器而不定义其实现:
var setterVisibility: String = "abc"
private set // 此 setter 是私有的并且有默认实现
var setterWithAnnotation: Any? = null
@Inject set // 用 Inject 注解此 setter
如果只读属性的值在编译期是已知的,那么可以使用 const 修饰符将其标记为编译期常量 这种属性需要满足以下要求:
- 位于顶层或者是 object 声明 或 companion object 的一个成员
- 以 String 或原生类型值初始化
- 没有自定义 getter
延迟初始化属性与变量,你可以用 lateinit 修饰符标记该属性
class Test{
lateinit var name:String
}
接口
你可以在接口中定义属性。在接口中声明的属性要么是抽象的,要么提供访问器的实现 访问器 = get方法
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())的方法。