Kotlin Reference: Classes and Inheritance

Classes

  Kotlin 中使用关键字 class 声明类:

[code lang=”kotlin”]class Invoice {
}[/code]

类的声明包括类名、类头(Class Header,用于指定类型参数、主构造器等)和用花括号包围的类体(Class Body)。类头和类体都是可选的,如果没有类体,则花括号也可以省略。

[code lang=”kotlin”]class Empty[/code]

Constructors

  Kotlin 的类可以有一个主构造器(Primary Constructor),以及一个或多个次构造器(Secondary Constructor)。主构造器是类头的一部分,写在类名(和可选的类型参数)之后:

[code lang=”kotlin”]class Person constructor(firstName: String) {
}[/code]

  如果主构造器没有任何可见性修饰符和注解,则可以省略 constructor 关键字:
[code lang=”kotlin”]class Person(firstName: String) {
}[/code]

  主构造器中不能包含代码,初始化代码要放在初始化块(Initializer Blocks)中,初始化块使用 init 关键字作为前缀:

[code lang=”kotlin”]class Customer(name: String) {
init {
logger.info(“Customer initialized with value ${name}”)
}
}[/code]

  注意主构造器的参数不仅可以在初始化块中使用,还可以用于初始化在类体中声明的属性:

[code lang=”kotlin”]class Customer(name: String) {
val customerKey = name.toUpperCase()
}[/code]

  实际上,对于声明属性并通过主构造器进行初始化的场景,Kotlin 提供了更简洁的写法:

[code lang=”kotlin”]class Person(val firstName: String, val lastName: String, var age: Int) {
// …
}[/code]

在主构造器中声明的属性可以是可变的(Mutable)var,也可以是只读的(Read-Only)val,就像普通的属性一样。

  如果主构造器有注解或可见性修饰符,则不能省略 constructor 关键字:

[code lang=”kotlin”]class Customer public @Inject constructor(name: String) { … }[/code]

  更多信息请参考可见性修饰符

Secondary Constructors

  在类中还可以声明次构造器,以 constructor 关键字作为前缀:

[code lang=”kotlin”]class Person {
constructor(parent: Person) {
parent.children.add(this)
}
}[/code]

  如果一个类已经有了主构造器,那么次构造器都要直接或间接(通过其他次构造器)地委托主构造器,使用 this 关键字来委托同一个类中的其他构造器:

[code lang=”kotlin”]class Person(val name: String) {
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}[/code]

  如果一个非抽象类没有声明任何构造器(主构造器或次构造器),则它会具有一个自动生成的无参主构造器,该构造器的可见性为 public。如果不想让类具有 public 构造器,则必须手动声明一个具有非默认可见性的空的主构造器:

[code lang=”kotlin”]class DontCreateMe private constructor () {
}[/code]

  注意:在 JVM 上,如果主构造器的所有参数都有默认值,则编译器会为其额外生成一个使用了这些默认值的无参构造器,以便于在 Kotlin 中使用如 Jackson 和 JPA 等利用无参构造器创建类的实例的库。

[code lang=”kotlin”]class Customer(val customerName: String = “”)[/code]

Creating instances of classes

  直接调用构造器来创建类的实例,就像调用普通的函数一样:

[code lang=”kotlin”]val invoice = Invoice()

val customer = Customer(“Joe Smith”)[/code]

  注意在 Kotlin 中没有 new 关键字,

  创建嵌套、内部和匿名内部类的方法见 嵌套类

Class Members

  类中可以包含:

Inheritance

  Kotlin 中的所有类都有一个公共的超类 Any,如果一个类没有声明超类,则其默认超类是 Any

[code lang=”kotlin”]class Example // Implicitly inherits from Any[/code]

  Any 与 Java 中的 java.lang.Object 不同,它除了 equals()hashCode()、和 toString() 外没有任何方法。详情请参考Java interoperability。【注】当 Java 类型被导入到 Kotlin 时,java.lang.Object 的引用会转换为 Any,Kotlin 通过扩展函数(Extension Function)来实现 java.lang.Object 的其他方法,如 wait()notify()getClass() 等。

  在类头中使用冒号后加类名的形式显式地声明超类:

[code lang=”kotlin”]open class Base(p: Int)
class Derived(p: Int) : Base(p)[/code]

  如果子类有主构造器,则必须在主构造器中使用主构造器的参数完成基类的初始化。

  如果子类没有主构造器,则其所有的次构造器必须使用 super 关键字初始化其基类,或者委托给其他构造器。注意此时不同的次构造器可以调用基类的不同的构造器。

[code lang=”kotlin”]class MyView : View {
constructor(ctx: Context) : super(ctx)

constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}[/code]

  类上的 open 标注与 Java 中的 final 相反,open 表示一个类可以被继承,Kotlin 中的类默认都是 final 的,对应 Effective Java 第 17 条:要么为继承而设计,并提供文档说明,要么就禁止继承(Item 17: Design and document for inheritance or else prohibit it)。

Overriding Methods

  正如上面提到的,我们坚持让 Kotlin 中的事物保持显式。不同于 Java,Kotlin 要求显式地标注可供覆盖的成员(我们称之为 open)和进行覆盖的成员:

[code lang=”kotlin”]open class Base {
open fun v() {}
fun nv() {}
}

class Derived() : Base() {
override fun v() {}
}[/code]

必须使用 override 标注 Derived.v(),否则编译器就会报错。如果函数没有标注为 open ,如 Base.nv(),则无论是否使用 override,在子类中声明具有同样签名的方法都是非法的。final 类(即没有标注为 open 的类)不能具有 open 成员。

  使用 override 标注的成员默认是 open 的,可以继续被子类覆盖。如果想要禁止再次覆盖,可以使用 final

[code lang=”kotlin”]open class AnotherDerived() : Base() {
final override fun v() {}
}[/code]

Overriding Properties

  覆盖属性和覆盖方法类似,在派生类中重新定义超类的属性时,必须在前面加上 override,且它们必须具有相兼容的类型。已经声明的属性可以被带初始化器的属性或带 getter 方法的属性覆盖:

[code lang=”kotlin”]open class Foo {
open val x: Int get { … }
}

class Bar1 : Foo() {
override val x: Int = …
}[/code]

  可以把 val 属性覆盖为 var,但不能反过来。因为 val 属性本质上声明了一个 getter 方法,而把它覆盖为 var 时会在派生类中额外声明一个 setter 方法。

  注意可以在主构造器的属性声明中使用 override 关键字:

[code lang=”kotlin”]interface Foo {
val count: Int
}

class Bar1(override val count: Int) : Foo

class Bar2 : Foo {
override var count: Int = 0
}[/code]

Overriding Rules

  在 Kotlin 中,实现继承遵守如下的规则:如果一个类从其直接超类中继承了一个成员的多个实现,则它必须要覆盖该成员,并提供自己的实现(可以选择调用继承来的实现之一)。使用 super 关键字并在尖括号中限定超类的名称,如 super<Base>,来指明所要引用的超类:

[code lang=”kotlin”]open class A {
open fun f() { print(“A”) }
fun a() { print(“a”) }
}

interface B {
fun f() { print(“B”) } // interface members are ‘open’ by default
fun b() { print(“b”) }
}

class C() : A(), B {
// The compiler requires f() to be overridden:
override fun f() {
super&ltA&gt.f() // call to A.f()
super&ltB&gt.f() // call to B.f()
}
}[/code]

C 同时继承了 AB,并继承了 a()b() 的一个实现,至此没有问题。但 C 继承了 f() 的两种实现, 所以我们必须在 C 中覆盖 f() 并给出自己的实现,以消灭歧义。

Abstract Classes

  类和其中的成员可以声明为 abstract,抽象成员在其所在的类中没有实现。注意我们不需要再标记抽象类或函数为 open,因为这是理所当然的。

  可以把非抽象的 open 成员覆盖为抽象成员:

[code lang=”kotlin”]open class Base {
open fun f() {}
}

abstract class Derived : Base() {
override abstract fun f()
}[/code]

Companion Objects

  Kotlin 的类没有静态方法,这一点与 Java 和 C# 不同。多数情况下,建议使用包级函数来替代。

  如果需要让函数能够不依赖对象实例,并能够访问类的内部(如工厂方法),可以在类中以对象声明的成员的形式编写。

  具体来说,如果你在类中声明了一个伴生对象,就可以仅用类名作为限定来调用伴生对象的成员,就像调用 Java / C# 的静态方法一样。