Kotlin面向对象之类与继承(Classes and Inhe
类(Classes)
在Kotlin中,类使用class关键字声明:
class Invoice {
}
类的声明由类名,类头(指定其类型参数,主构造函数等)和类体组成,类体由大括号括起来。类头和类体都是可选的; 如果一个类没有类体,则可以省略花括号,如下:
class Empty
构造器(Constructors)
在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)的也可以是只读(read-only)的。
若主构造器被注解或可见性操作符修饰,则constructor关键字不可省略,修饰符在constructor关键字的前面:
class Customer public @Inject constructor(name: String) { ... }
副构造器(Secondary Constructors)
类也可以通过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的构造器,你需要手动声明一个private的非默认主构造器:
class DontCreateMe private constructor () {
}
注意:在JVM虚拟机中,如果主构造器的所有参数都有默认值,编译器将额外生成一个无参的构造器,该构造器将使用主构造器中的默认值。这使Kotlin的使用变的更加容易,在使用诸如Jackson或JPA的库时,可以通过无参数构造函数创建类实例:
class Customer(val customerName: String = "")
创建类的实例(Creating instances of classes)
要创建一个类的实例,我们可以通过调用构造器的方式来完成,就像调用一个常规函数一样:
val invoice = Invoice()
val customer = Customer("Joe Smith")
注意:Kotlin中没有new关键字。
关于嵌套类、内部类以及匿名内部类的创建方式,在Nested Classes介绍。
类成员(Class Members)
一个类可以包含:
- 构造器与初始化块
- 函数Functions
- 属性Properties
- 嵌套和内部类Nested and Inner Classes
- 对象声明Object Declarations
对于新知识点,将后面的内容中逐步介绍到。
继承(Inheritance)
Kotlin中的所有类都有一个公用的基类:Any,该类是没有显式声明继承关系的类的父类:
class Example // Implicitly inherits from Any
Any
类不是java.lang.Object
类:事实上,Any类除了equals()、hashCode()、toString()
方法室外没有任何的其他成员。关于更多细节,参见这里
为了明确表达继承关系,可以将父类型放置在类头的最后边,并以冒号分割:
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)
}
Kotlin中,open
注解的含义和Java中的final
是相反的:open允许其他类作为该类的子类。默认地,Kotlin中的所有类(包括方法)都被隐式声明为final。
方法重载(Overriding Methods)
如前所述,使用Kotlin应坚持显式声明(因为默认都是final)。因此,不像Java,Kotlin需要对可覆盖成员进行显式注解(我们称之为open)并重写:
open class Base {
open fun v() {}
fun nv() {}
}
class Derived() : Base() {
override fun v() {}
}
Derived
类的v()
函数由override
注解修饰。如果该方法没有被该注解修饰,则编译器将会报错。如果一个方法没有被open
修饰,向Base
的nv()
方法,在子类中声明一个同样的方法签名将是非法的,无论子类中的方法是否被override
修饰。在一个final
类中,即没有被open
修饰的类,用open
修饰该类的成员是被禁止的。
一个被override
修饰的成员,默认是open
的,它可以被子类重写。如果你想禁止子类重写该override
方法,应使用final
关键字显式声明:
open class AnotherDerived() : Base() {
final override fun v() {}
}
重写属性(Overriding Properties)
属性的重写与方法的重写非常类似,父类声明的属性,若在子类中被重新声明,则应以override
修饰,且它们必须互相兼容。每个被声明的属性可以通过属性初始化或该属性的getter
方法被重写:
open class Foo {
open val x: Int get { ... }
}
class Bar1 : Foo() {
override val x: Int = ...
}
可以将val属性重写为var属性,反之则不行。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
}
重写规则(Overriding Rules)
在Kotlin中,实现继承需要遵循以下规则:如果一个类从其直接父类中继承了同一成员的多个实现,则它必须覆盖该成员并提供自己的实现(当然,也可以使用其中一个父类的实现)。为了在自己的实现中表示父类的实现,我们使用super
关键字,并在尖括号中使用父类名称进行限定,也就是super<Base>
:
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只继承了每些个函数的一个实现。 但是对于f(),我们从C继承了两个实现,因此我们必须在C中重写f(),并提供我们自己的实现来消除歧义。
抽象类(Abstract Classes)
一个类和它的成员可以被abstract
关键字修饰。一个抽象成员在该类中不能有其实现。需要注意的是我们不必对一个抽象类或抽象方法声明open,这是毫无疑问的。
在一个抽象类中,我们可以重写一个非抽象的但open的方法(也就是将一个非抽象方法重写为抽象方法):
open class Base {
open fun f() {}
}
abstract class Derived : Base() {
override abstract fun f()
}
伴生对象(Companion Objects)
Kotlin不像Java或C#,类没有静态方法。常见的,推荐仅适用包级函数来代替。
如果需要编写一个不需要类实例就可以调用的函数,且需要访问一个类的内部(例如,一个工厂方法),则可以将其作为类内部的Object Declarations成员。
更具体地说,如果您在类中声明了一个伴生对象,则可以使用与在Java / C#调用静态方法相同的语法:使用类名作为限定符来访问其成员。