Kotlin类与对象篇(1)--类与继承(Inheritance)


欢迎关注 二师兄Kotlin
转载请注明出处 二师兄kotlin


对象

Kotlin中对象使用 class关键字来进行声明:

class Invoice {
}

类的声明由三部分组成:类名、类头(具体化的类型参数、主构造函数等)以及类体,被一堆花括号包围{}( curly braces)。类头和类体均是可选的。如果类没有类体,花括号也可以被删掉。

class Empty

构造函数(Constructors)

Kotlin中的类可以包含 一个主构造函数和多个次构造函数。而主构造函数是类头的一部分:紧随在类名之后(类型参数可选)。

class Person constructor(firstName: String) {
}

主构造函数不能包含任何代码。初始化代码必须放置在 初始化块(initializer blocks)中,通过关键字init作为前缀。

在初始化一个实例的过程中,初始化块按照出现在类体中的顺序依次执行,可以和属性初始化进行穿插。

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) { ... }

想了解更多细节,可以看Visibility Modifiers.

次构造函数

类也可以声明次要构造器,次要构造器以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)
    }
}

注意:在初始化代码块的代码高效地成为首要构造器的一部分。首要构造器的代理会作为次要构造器的第一行语句执行,所以在所有初始化代码块的代码都在次要构造器之前执行。甚至,如果类没有首要构造器,代理都会隐式地发生,初始化代码块依旧会执行:

class Constructors {
    init {
        println("Init block")
    }

    constructor(i: Int) {
        println("Constructor")
    }
}

如果,一个非抽象类没有声明任何构造器(包括首要和次要),类仍然会生成一个没有参数的首要构造器。该构造器的可见性是public、如果你不想你的类有一个public构造器,你需要声明一个空的首要构造器,并使用非默认可见性修饰符:

class DontCreateMe private constructor () {
}

NOTE:注意: 在JVM中,如果首要构造器的所有参数都有默认数值,编译器将会生成一个额外的无参数的构造器,该构造器会使用默认数值。这样使得更容易在Kotlin中使用库,例如Jackson或者JPA(通过无参数构造器创建类的实例)

class Customer(val customerName: String = "")

创建类的实例

为了创建类的实例,我们将如调用普通函数一样调用构造器:

val invoice = Invoice()
val customer = Customer("Joe Smith")
  • 注意Kotlin中不使用关键字new
    嵌套类、内部类、匿名内部类的创建在嵌套类中Nested classes
    进行介绍。

类的成员

类可以包含:

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

继承

在Kotlin中所有类都有一个共同的超类Any, 该超类是默认的,不需要超类的声明:

class Example // 隐式继承自Any

Any不是java.lang.Object;特别地,它除了equals(), hashCode() and toString()没有其他任何成员。请参考Java interoperability
(Java互通性)章节。

为了声明一个显式的超类,我们将类型放置在类头后的冒号后面:

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相反的内容:open允许其他类继承自该类。默认的,在Kotlin中所有的类都是final(重点),这对应于Effective Java
, Item 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() {}
}

override 注解需要修饰Derived.v(). 如果该注解遗漏,编译器就会报错。如果一个函数没有open注解,类似Base.nv(), 在子类中声明相同签名的方法就是非法的,无论是否有overide。在一个final类中(即,一个没有open注解的类), 禁止拥有open的成员(open修饰符无效).

overide标记的成员其本身就是open的,即,该成员可以在子类被重写。如果你想禁止“再重写”(重写父类的方法继续被子类重写),可以使用final关键字:

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

属性重写

与方法重写类似,在父类中已经声明的属性,如果要在一个派生类(derived class)中再次声明, 则必须使用override打头,而且他们必须类型可以兼容。一个拥有初始化或者getter方法的属性都可以去重写旧属性。

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

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

你可以用一个var属性去重写一个val属性,但是,但是反过来则不行(vice versa)。这样被允许的原因是一个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关键字: super@Outer:

class Bar : Foo() {
    override fun f() { /* ... */ }
    override val x: Int get() = 0
    
    inner class Baz {
        fun g() {
            [email protected]() // Calls Foo's implementation of f()
            println([email protected]) // Uses Foo's implementation of x's getter
        }
    }
}

重写规则

在Kotlin中,继承的实现被这样一条规则所限制:如果一个类继承了他的多个超类中的同一个函数的多个实现,那么他必须重写这个函数且提供自己的实现(比如,使用所继承实现中的某一个实现)。为了表示你到底是用了哪个超类中哪个实现,我们可以使用 尖括号(angle brackets)包围的超类类名来标记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.f() // call to A.f()
        super.f() // call to B.f()
    }
}

继承AB没有问题,而且对于在继承类C中对于函数a()b()的实现也没有问题。但是对于函数f(),对于C来说我们有两个实现,所以我们必须去重写f()并且提供自己的实现来消除歧义(eliminates the ambiguity)。

抽象类

类和他的一些成员可以声明成abstract。一个抽象成员不可以包含实现。注意我们不需要用open来标注一个抽象类或者他的函数,默认就是open(it goes without saying 不用说)。

我们可用一个抽象的成员来重写一个非抽象、但开放的成员。

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

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

友元对象(Companion Objects)

在Kotlin中,不想java或者C#,类没有静态方法。大多数情况下,更推荐使用 包级方法(package-level functions )来替代。

如果你需要写这样一个函数,他可以访问一个类的内部但并非通过类对象实例(比如,一个工厂方法),你可以把它写成一个object declaration成员放在类中。

更具体地说(Even more specifically ),如果你在类中声明了一个

companion object,你将可以实现像调用Java/C#中静态方法那样的调用语法来调用companion object的成员,只需要类名作为标识。

你可能感兴趣的:(Kotlin类与对象篇(1)--类与继承(Inheritance))