Kotlin 类与继承

原文:http://kotlinlang.org/docs/reference/classes.html

类与继承

在 Kotlin 中,使用关键字 class 定义类

class Invoice {
}

类的定义包括类的名字,类的头部(指定参数类型,主构造方法等)和花括号包围的类体。类的头部和类体都是可选的,如果类没有类体,花括号可以省略。

class Empty

构造方法

Kotlin 中的类有一个主构造方法和一个或多个从构造方法,主构造方法是类头部的一部分,放在类名之后(和可选的类型参数)

class Person constructor(firstName: String) {
}

如果主构造方法没有任何注解或者可见性修饰符,constructor 关键字可以省略:

class Person(firstName: String) {
}

主构造方法不能包含任何代码,初始化代码可以放在初始化块中,初始化块前需要使用 init 关键字:

class Customer(name: String) {
    init {
        logger.info("Customer initialized with value ${name}")
    }
}

主构造方法的参数可以在初始化块中使用,这些参数同样可以用于初始化类体中定义的属性

class Customer(name: String) {
    val customerKey = name.toUpperCase()
}

实际上,在主构造方法中定义属性并初始化,Kotlin 有更精简的语法,

class Person(val firstName: String, val lastName: String, var age: Int) {
    // ...
}

同常规的属性一样,主构造方法中定义的属性可以是可变的(var)或者是只读的(val)。

如果构造方法有注解或者可见性修饰符修饰,constructor 关键字就是必须的,修饰符要放到 constructor 之前

class Customer public @Inject constructor(name: String) { ... }

更多细节,请参考 Visibility Modifier

从构造方法

类可以定义从构造方法,以 constructor 作为前缀

class Person {
    constructor(parent: Person) {
        parent.children.add(this)
    }
}

如果类有一个主构造方法,每一个从构造方法都需要直接或间接通过其他从构造方法来调用主构造方法。调用同一个类的其他构造方法使用 this 关键字。

class Person(val name: String) {
    constructor(name: String, parent: Person) : this(name) {
        parent.children.add(this)
    }
}

如果一个非抽象类没有定义任何构造方法(主或者从),系统会为它生成一个无参的主构造方法。生成的构造方法是 public 的。如果你不想你的类有一个 public 的构造方法,你需要定义一个私有的空的主构造方法,

class DontCreateMe private constructor () {
}

在 JVM 中,如果主构造方法的所有参数都有默认值,编译器会使用默认值生成一个额外的无参的构造方法。这使得使用 Kotlin库例如Jackson或者 JPA,通过无参构造方法创建类的实例变得更加容易。

class Customer(val customerName: String = "")

创建类的实例

创建类的实例,我们可以像调用常规方法一样调用构造方法

val invoice = Invoice()

val customer = Customer("Joe Smith")

注意,Kotlin 没有 new 关键字

创建嵌套实例,内部或者匿名内部类可以参考 Nested classes

类的成员

一个类可以包含:

  • 构造方法和初始化块
  • 方法
  • 属性
  • 嵌套内部类
  • 对象声明

继承

Kotlin 中所有类都有一个公共的父类 Any,也即是说如果一个类没有声明父类,它会默认继承自Any:

class Example // 隐式继承Any

Any 不是 java.lang.Object,特别地,除了equals(), hashCode() 和 toString()它没有任何成员。更多细节请参考 Java interoperability章节

显示的声明父类需要在类头部之后的冒号后放置父类

open class Base(p: Int)

class Derived(p: Int) : Base(p)

如果一个类有主构造方法,必须使用主构造方法的参数立即初始化父类

如果一个类没有主构造方法,每一个从构造方法需要使用 super 关键字或者调用其他构造方法初始化父类,本例中不同的从从构造方法可以调用父类不同的构造方法。

class MyView : View {
    constructor(ctx: Context) : super(ctx)

    constructor(ctx: Context, attrs: AttributeSet) : super(ctx, attrs)
}

类上的 open 注解与 Java 的 final 正好相反,它允许其他的类继承。默认情况下,Kotlin 的所有类都是 final 的,这个规则对应 Effective Java 第17条原则: Design and document for inheritance or else prohibit it.

覆盖方法

我们之前提过,在 Kotlin 中我们遵守着将事情变得更加明确这一原则。与 Java 不同,Kotlin 要求父类可以被继承的成员(我们称为open的)必须显示的声明注解

open class Base {
    open fun v() {}
    fun nv() {}
}
class Derived() : Base() {
    override fun v() {}
}

Derived类的v方法,必须声明override注解,如果没有声明 override,编译器会报错。如果一个方法上没有 open 注解,就像 Base 类的v方法,在子类中声明同样签名的方法是不合法的,无论它是否有 override 注解。在final 类中(一个类没有open注解)禁止定义open成员。

一个标有override注解的成员自己是open的,即,它可能在子类中被覆盖,如果你想禁止覆盖,请使用final

open class AnotherDerived() : Base() {
    final override fun v() {}
}

覆盖属性

覆盖属性与覆盖方法的方式是相似的;子类重新定义父类中声明的属性必须加override关键字,而且它们的类型必须是兼容的。每一个属性都可以被带有初始化方法的属性覆盖,或者被带有getter方法的属性覆盖。

open class Foo {
    open val x: Int get() { ... }
}

class Bar1 : Foo() {
    override val x: Int = ...
}

你还可以用var(可变的)属性覆盖一个val(不可变)的属性,反之则不然。允许这个原则是因为,每一个 val 属性都会声明getter方法,而将其重写为一个var属性,只是在子类中额外的增加了一个setter方法。

你可以在主构造方法中使用override关键字声明属性。

interface Foo {
    val count: Int
}

class Bar1(override val count: Int) : Foo

class Bar2 : Foo {
    override var count: Int = 0
}

调用父类的实现

子类中的代码可以使用super关键字调用父类的方法或者属性访问器的实现。

open class Foo {
    open fun f() { println("Foo.f()") }
    open val x: Int get() = 1
}

class Bar : Foo() {
    override fun f() { 
        super.f()
        println("Bar.f()") 
    }

    override val x: Int get() = super.x + 1
}

在内部类中,可以使用super@父类名的方式访问外部类的父类。

class Bar : Foo() {
    override fun f() { /* ... */ }
    override val x: Int get() = 0

    inner class Baz {
        fun g() {
            super@Bar.f() // Calls Foo's implementation of f()
            println(super@Bar.x) // Uses Foo's implementation of x's getter
        }
    }
}

覆盖原则

在 Kotlin 中实现继承需要遵循以下规则:

  • 如果一个类继从其直接父类继承了多个相同成员的实现,它必须重写这个成员来提供自己的实现(或者使用它所继承的其中之一)。在子类中调用其所继承父类的实现,使用super加上尖括号指定的父类,例如:super
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<A>.f() // call to A.f()
        super<B>.f() // call to B.f()
    }
}

继承A与B是没问题的。继承a()方法和b()方法也是没问题的因为C只继承了各自的一个实现。但是C继承了f()方法的两个实现,因此,我们必须覆盖f()方法来提供自己的实现以消除歧义。

抽象类

一个类或者其成员可以被声明为abstract的,类的抽象成员没有实现,我们不需要使用open标注一个抽象类或方法,因为默认就是open的。

子类可以覆盖非abstract的父类或方法,并将其声明为abstract

open class Base {
    open fun f() {}
}

abstract class Derived : Base() {
    override abstract fun f()
}

同伴对象

在Kotlin中不像Java或者C#,类没有静态方法。大多数情况下,推荐使用包级别的函数来替代。

如果你需要定义一个函数,在没有类的实例的情况下就可以调用,但是又需要访问类的内部结构(例如工厂方法),你可以在那个类中将这个函数定义为一个object declaration成员

特别地,如果你在类中声明一个同伴对象,你可以像调用Java/C#的静态方法一样,使用类名调用其成员。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值