7.2 类与构造函数

Kotlin和Java很相似,也是一种面向对象的语言。下面我们来一起学习Kotlin的面向对象的特性。如果您熟悉Java或者C++、C#中的类,您可以很快上手。同时,您也将看到Kotlin与Java中的面向对象编程的一些不同的特性。

Kotlin中的类和接口跟Java中对应的概念有些不同,比如接口可以包含属性声明;Kotlin的类声明,默认是final和public的。

另外,嵌套类并不是默认在内部的。它们不包含外部类的隐式引用。

在构造函数方面,Kotlin简短的主构造函数在大多数情况下都可以满足使用,当然如果有稍微复杂的初始化逻辑,我们也可以声明次级构造函数来完成。

我们还可以使用 data 修饰符来声明一个数据类,使用 object 关键字来表示单例对象、伴生对象等。

Kotlin类的成员可以包含:

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

7.2.1 声明类

和大部分语言类似,Kotlin使用class作为类的关键字,当我们声明一个类时,直接通过class加类名的方式来实现:

  1. class World

这样我们就声明了一个World类。

7.2.2 构造函数

在 Kotlin 中,一个类可以有

  • 一个主构造函数(primary constructor)
  • 一个或多个次构造函数(secondary constructor)

主构造函数

主构造函数是类头的一部分,直接放在类名后面:

  1. open class Student constructor(var name: String, var age: Int) : Any() {
  2. ...
  3. }

如果主构造函数没有任何注解或者可见性修饰符,可以省略这个 constructor 关键字。如果构造函数有注解或可见性修饰符,这个 constructor 关键字是必需的,并且这些修饰符在它前面:

  1. annotation class MyAutowired
  2. class ElementaryStudent public @MyAutowired constructor(name: String, age: Int) : Student(name, age) {
  3. ...
  4. }

与普通属性一样,主构造函数中声明的属性可以是可变的(var)或只读的(val)。

主构造函数不能包含任何的代码。初始化的代码可以放到以 init 关键字作为前缀的初始化块(initializer blocks)中:

  1. open class Student constructor(var name: String, var age: Int) : Any() {
  2. init {
  3. println("Student{name=$name, age=$age} created!")
  4. }
  5. ...
  6. }

主构造的参数可以在初始化块中使用,也可以在类体内声明的属性初始化器中使用。

次构造函数

在类体中,我们也可以声明前缀有 constructor 的次构造函数,次构造函数不能有声明 val 或 var :

  1. class MiddleSchoolStudent {
  2. constructor(name: String, age: Int) {
  3. }
  4. }

如果类有一个主构造函数,那么每个次构造函数需要委托给主构造函数, 委托到同一个类的另一个构造函数用 this 关键字即可:

  1. class ElementarySchoolStudent public @MyAutowired constructor(name: String, age: Int) : Student(name, age) {
  2. override var weight: Float = 80.0f
  3. constructor(name: String, age: Int, weight: Float) : this(name, age) {
  4. this.weight = weight
  5. }
  6. ...
  7. }

如果一个非抽象类没有声明任何(主或次)构造函数,它会有一个生成的不带参数的主构造函数。构造函数的可见性是 public。

私有主构造函数

我们如果希望这个构造函数是私有的,我们可以如下声明:

  1. class DontCreateMe private constructor() {
  2. }

这样我们在代码中,就无法直接使用主构造函数来实例化这个类,下面的写法是不允许的:

  1. val dontCreateMe = DontCreateMe() // cannot access it

但是,我们可以通过次构造函数引用这个私有主构造函数来实例化对象:

7.2.2 类的属性

我们再给这个World类加入两个属性。我们可能直接简单地写成:

  1. class World1 {
  2. val yin: Int
  3. val yang: Int
  4. }

在Kotlin中,直接这样写语法上是会报错的:

Kotlin极简教程

意思很明显,是说这个类的属性必须要初始化,或者如果不初始化那就得是抽象的abstract属性。

我们把这两个属性都给初始化如下:

  1. class World1 {
  2. val yin: Int = 0
  3. val yang: Int = 1
  4. }

我们再来使用测试代码来看下访问这两个属性的方式:

  1. >>> class World1 {
  2. ... val yin: Int = 0
  3. ... val yang: Int = 1
  4. ... }
  5. >>> val w1 = World1()
  6. >>> w1.yin
  7. 0
  8. >>> w1.yang
  9. 1

上面的World1类的代码,在Java中等价的写法是:

  1. public final class World1 {
  2. private final int yin;
  3. private final int yang = 1;
  4. public final int getYin() {
  5. return this.yin;
  6. }
  7. public final int getYang() {
  8. return this.yang;
  9. }
  10. }

我们可以看出,Kotlin中的类的字段自动带有getter方法和setter方法。而且写起来比Java要简洁的多。

7.2.3 函数(方法)

我们再来给这个World1类中加上一个函数:

  1. class World2 {
  2. val yin: Int = 0
  3. val yang: Int = 1
  4. fun plus(): Int {
  5. return yin + yang
  6. }
  7. }
  8. val w2 = World2()
  9. println(w2.plus()) // 输出 1