本文主要介绍 Kotlin
类与对象。
看了官方文档的相关描述,发现很多名词在 Kotlin
中和 Java
都有不太一样,在 Kotlin
中 方法
被称为 函数
,而像 open
, final
这种关键字被称为注解(annotation
),在我之前的认知当中只有 @Inject
这种才是注解。因此文章中的描述都尽量使用 Kotlin
中的术语。
构造器
Kotlin
中的类可以有一个 主构造器 (primary constructor
), 以及一个或多个 次构造器 (secondary constructor
)。主构造器是类头部的一部分, 位于类名称(以及可选的类型参数)之后。
主构造器
主构造器紧跟在类名后面进行声明,声明的一般形式如下,可见度修饰符默认为 public
,(如果主构造器是 public
的,同时没有注解那么可以省略 constructor
关键字)。当然如果类内部没有具体的实现,{}
也是可以省略的。
1 | // 完整的写法 |
由于主构造器中无法使用初始化代码,可以在类内进行属性的初始化,使用 init{}
关键字表示。
1 | class Engineer (name: String, age: Int, language: String) { |
在主构造器内可以直接使用 val
和 var
声明和初始化属性,他们都是类的成员属性。
1 | class Engineer(val name: String = "", age: Int = 0, language: String = "") { |
注意: 在 JVM 中, 如果主构造器的所有参数都指定了默认值, 编译器将会产生一个额外的无参数构造器, 这个无参数构造器会使用默认参数值来调用既有的构造器. 有些库(比如 Jackson 或 JPA) 会使用无参数构造器来创建对象实例, 这个特性将使得 Kotlin 比较容易与这种库协同工作.
次级构造器
如果类有主构造器, 那么每个次级构造器都必须委托给主构造器, 要么直接委托, 要么通过其他次级构造器间接委托. 委托到同一个类的另一个构造器时, 使用 this
关键字实现。
1 | class Engineer(val name: String = "", age: Int = 0, language: String = "") { |
创建对象
构造器中的属性指定初始值后在创建对象时可以省略,使用如下方法,避免重载构造方法,主构造器中具有默认值的属性不是必须赋值的。
1 | val en = Engineer("",12) |
继承
kotlin
中所有的类默认继承自超类 Any
,Any
不是 java.lang.Object
; 尤其要注意, 除 equals()
, hashCode()
和 toString()
之外, 它没有任何成员。 Kotlin
中所有的类都是 fnial
的,也就是不允许继承,这点和 Java
正好相反,如果你想继承一个类必须使用 open
关键字声明。
Kotlin
使用 :
表示继承,子类和父类构造器调用分为以下两种情况
- 如果类有主构造器, 那么必须在主构造器中使用主构造器的参数来初始化基类。
1 | open class Engineer(var name: String = "", var age: Int = 0, var language: String = "") { |
- 如果类没有主构造器, 那么所有的次级构造器都必须使用
super
关键字来初始化基类, 或者委托到另一个构造器, 由被委托的构造器来初始化基类。 注意, 这种情况下, 不同的次级构造器可以调用基类中不同的构造器。
1 | open class Engineer(var name: String = "", var age: Int = 0, var language: String = "") { |
方法的覆盖
和类一样,类中的所有方法都默认是 final
的,即不允许在子类中进行重写,如果想要在子类中更改,需要使用 open
注解声明,如实例中 3
。
如果子类想要覆盖父类的方法,必须添加 override
注解。 如果遗漏了这个注解,编译器将会报告错误。 如果一个函数没有标注 open
注解, 那么在子类中声明一个同名同参的方法将是非法的, 无论是否添加 override
注解, 都不可以,如实例中 4
。
当一个子类成员标记了 override
注解来覆盖父类成员时, 覆盖后的子类成员本身也将是 open
的, 也就是说, 子类成员可以被自己的子类再次覆盖,如实例中 1
。 如果你希望禁止这种再次覆盖,可以使用 final
注解,如实例中 2
。
实例:
1 | open class Engineer(var name: String = "", var age: Int = 0, var language: String = "") { |
在一个 final
类(比如, 一个没有添加 open
注解的类)中, 声明 open
成员是没有意义的。
1 | class Test{ |
属性的覆盖
前提,属性的覆盖方式与方法覆盖类似,同样必须在父类中声明为 open
,当超类中声明的属性在后代类中再次声明时,必须使用 override
关键字来标记,而且覆盖后的属性数据类型必须与超类中的属性数据类型兼容。
可以使用带初始化器的属性来覆盖超类属性, 也可以使用带取值方法(getter
)的属性来覆盖。
可以在主构造器的属性声明中使用 override
关键字,覆盖主构造器中的属性。
可以使用一个 var
属性覆盖一个 val
属性,但不可以反过来使用一个 val
属性覆盖一个 var
属性。 允许这种覆盖的原因是, val
属性本质上只是定义了一个 get
方法,使用 var
属性来覆盖它, 只是向后代类中添加了一个 set
方法。
1 | // name属性是open的,它可以在子类中被覆盖 |
覆盖的原则
在 Kotlin
中, 类继承中的方法实现问题, 遵守以下规则: 如果一个类从它的直接超类中继承了同一个成员的多个实现,那么这个子类必须覆盖这个成员,并提供一个自己的实现,这样在子类中消除歧义,为了表示使用的方法是从哪个超类继承得到的, 我们使用 super
关键字, 将超类名称放在尖括号类, 比如 super<父类>
。
1 | open class ClsA { |