Kotlin 作为类 Java 语言,在面向对象上具有与 Java 相似的特性,但是针对不同的情况进行了不同的优化,今天我们简单介绍一下 Kotlin 中的类和构造函数。

1. 定义类和创建类的实例

Kotlin 中定义类与 Java 相同,使用 class 关键字:

class 类名[主构造函数][{
  //类成员
}]

Kotlin 中的类名与 Java 相同,采用骆峰式命名法,首字母大写。

不同的是,Kotlin 中的类如果是空类,没有任何语句,则可以省略大括号(闲的)。

要创建一个类的实例,只需要调用类的构造函数,不使用 new 关键字

val s = StringBuilder("Hello World")val list: List<String> = ArrayList()


2. 主构造函数

Kotlin 类的构造函数与 Java 有较大区别。首先,Kotlin 把构造函数分为 主构造函数 和 次构造函数,主构造函数写在类头中,有且只有一个;次构造函数写在类语句中,可以有多个,也可以没有。

首先,我们看一个简单的 Java 类:

public class Person {
    String name;
    public Person(String name) {
        this.name = name;
    }}

这个类定义一个 String 类型的成员变量 name,然后定义了带有一个 String 类型参数的构造函数,这个构造函数把参数列表中的 name 赋给了成员变量 name。

用 Kotlin,我们可以这样写:

class Person constructor(name: String) {
    val name: String
    init {
        this.name = name
    }}

首先,我们使用 class 定义了一个类 Person,然后在类头用 constructor 关键字定义带有一个 String 类型参数的主构造函数。在类体里,我们定义了一个不可变的 String 类型成员变量 name,然后使用 init 关键字定义主构造函数的行为,把主构造函数的 name 参数赋给成员变量 name。

能看到,Kotlin 类的主构造函数,参数列表定义在类声明的部分,函数体却在 init 代码块里。(莫名其妙)

这样写不够简洁呀!实际上,充分利用 Kotlin 提供的特性,我们可以把这个类缩短到一行:

class Person(val name: String)

看吧,所有冗余信息都已除去,只保留了最关键的部分。想理解这句话,我们需要知道主构造函数定义中的两个细节:

  1. 如果主构造函数没有任何修饰符,则可以去掉 constructor 关键字。这样,我们的类定义就很像一个函数了:

    class Person(name: String) {/*……*/}

    如果想使用在主构造函数前使用修饰符,那么这个 constructor 就不能省了:

    class Person private constructor() {/*……*/}
  2. 如果主构造函数中定义的参数使用 val 或者 var 修饰,则会创建与这个参数同名的成员变量,并使用传入的参数值初始化这个成员变量。简单来说,就是把“定义成员变量”和“使用构造函数传入的参数初始化成员变量”简化为一个 val 或者 var 关键字。

    上面的例子中,我们在主构造方法里声明 val name: String,Kotlin 就会自动为我们添加一个名为 name 的不可变的 String 类型成员变量,然后用主构造函数传入的值初始化这个成员变量,可以直接调用。

3. 次构造函数

Java 中,一个类往往有多个不同的构造函数,它们一般有下面两种关系:

  1. 参数列表由少到多,参数列表少的构造函数使用 默认值 调用参数列表多的构造函数。对于这个常见的类型,Kotlin 中使用 函数默认参数 的方法简化代码;

  2. 不同的构造方法使用不同的参数列表,相互之间存在调用关系。Kotlin 中使用 次构造函数委托 的解决方法。

首先看第一种情况,这里我们给用 Java 写的 Person 类添加一些东西:

public class Person {
  long id;
  String name = "";
  int age = 0;

  public Person(long id, String name, int age) {
    this.id = id;
    this.name = name;
    this.age = age;
  }
  public Person(long id, String name) {
    this.id = id;
    this.name = name;
  }
  public Person(long id, int age) {
    this.id = id;
    this.age = age;
  }
  public Person(long id) {
    this.id = id;
  }}

我们添加了两个属性,一个 long 类型的 id,一个 int 类型的 age。三个构造函数都需要传入 id 变量,name 和 age 变量的要求则是 如果没有外部传入的参数,就使用默认值

使用 Kotlin,我们可以大大简化这一长串代码:

class Person(val id: Long, val name: String = "", val age: Int = 0)

好吧,Kotlin 又是只用一行就解决了问题……

我们重点看一下参数列表:

  • 参数列表中定义了三个参数,都使用 val 关键字修饰,说明要使用它们创建成员变量并初始化;

  • name 和 age 参数后面都使用 = 默认值 的方法声明了它们的默认值,在调用主构造函数的时候,如果不传入参数,就使用指定的默认值。

对于构造函数的第二种类型:使用没有关系的参数列表,但存在调用关系,Kotlin 中的处理方式是使用次构造函数委托主构造函数。我们接着改一下 Person 类:

public class Person {
  long id;
  String name = "";

  public Person(long id) {
    this.id = id;
  }
  public Person(String name) {
    this(name.hashCode());
    this.name = name;
  }}

使用 Kotlin,我们可以这样写:

class Person(val id: Long) {
  var name: String = ""

  constructor(name: String) : this(name.hashCode().toLong()) {
    this.name = name
  }}

首先,我们在主构造函数里声明了并初始化了成员变量 id,然后在类体内使用 constructor 关键字定义一个带有一个 String 类型参数的 次构造方法,这个次构造方法先调用主构造函数,然后将参数赋给成员变量。这里有几个需要注意的地方:

  1. 如果类已经有了一个主构造函数,那么所有的次构造函数都要直接或间接地委托给主构造函数。也可以先委托给其他的次构造函数,再由它们委托给主构造函数。所有的次构造函数都会先调用主构造函数,再执行自己特有的代码。写法:

    constructor([参数列表]): this([参数列表]) {/*……*/}
  2. 次构造函数不能在参数列表中声明并初始化成员变量,这也是上面“name: String”前面为什么没有 val 的原因。而且因为次构造函数会改动 name 的值,所以 name 必须声明为 var。