Kotlin 作为类 Java 语言,在面向对象上具有与 Java 相似的特性,但是针对不同的情况进行了不同的优化,今天我们简单介绍一下 Kotlin 中的类和构造函数。
1. 定义类和创建类的实例
Kotlin 中定义类与 Java 相同,使用 class 关键字:
class 类名[主构造函数][{
//类成员
}]
Kotlin 中的类名与 Java 相同,采用骆峰式命名法,首字母大写。
不同的是,Kotlin 中的类如果是空类,没有任何语句,则可以省略大括号(闲的)。
要创建一个类的实例,只需要调用类的构造函数,不使用 new 关键字:
val s = StringBuilder("Hello World")
val list: List = 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)
看吧,所有冗余信息都已除去,只保留了最关键的部分。想理解这句话,我们需要知道主构造函数定义中的两个细节:如果主构造函数没有任何修饰符,则可以去掉 constructor 关键字。这样,我们的类定义就很像一个函数了:
class Person(name: String) {/*……*/}
如果想使用在主构造函数前使用修饰符,那么这个 constructor 就不能省了:
class Person private constructor() {/*……*/}
如果主构造函数中定义的参数使用 val 或者 var 修饰,则会创建与这个参数同名的成员变量,并使用传入的参数值初始化这个成员变量。简单来说,就是把“定义成员变量”和“使用构造函数传入的参数初始化成员变量”简化为一个 val 或者 var 关键字。
上面的例子中,我们在主构造方法里声明 val name: String,Kotlin 就会自动为我们添加一个名为 name 的不可变的 String 类型成员变量,然后用主构造函数传入的值初始化这个成员变量,可以直接调用。
3. 次构造函数
Java 中,一个类往往有多个不同的构造函数,它们一般有下面两种关系:参数列表由少到多,参数列表少的构造函数使用 默认值 调用参数列表多的构造函数。对于这个常见的类型,Kotlin 中使用 函数默认参数 的方法简化代码;
不同的构造方法使用不同的参数列表,相互之间存在调用关系。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 类型参数的 次构造方法,这个次构造方法先调用主构造函数,然后将参数赋给成员变量。这里有几个需要注意的地方:如果类已经有了一个主构造函数,那么所有的次构造函数都要直接或间接地委托给主构造函数。也可以先委托给其他的次构造函数,再由它们委托给主构造函数。所有的次构造函数都会先调用主构造函数,再执行自己特有的代码。写法:
constructor([参数列表]): this([参数列表]) {/*……*/}
次构造函数不能在参数列表中声明并初始化成员变量,这也是上面“name: String”前面为什么没有 val 的原因。而且因为次构造函数会改动 name 的值,所以 name 必须声明为 var。