类 Kotlin 中的类使用 class
关键字定义:
class Person { /*...*/ }
类的定义由以下几部分组成: 类名, 类头部(指定类的类型参数, 主构造器, 以及其他内容), 以及由大括号括起的类主体部分. 类的头部和主体部分都是可选的; 如果类没有主体部分, 那么大括号也可以省略.
class Empty
构造器 Kotlin 中的类有一个 主构造器(primary constructor) , 此外还可以有一个或多个 次构造器(secondary constructor) . 主构造器在类头部中声明, 位于类名称以及可选的类型参数之后.
class Person constructor(firstName: String) { /*...*/ }
如果主构造器没有任何注解(annotation), 也没有任何可见度修饰符, 那么 constructor
关键字可以省略:
class Person(firstName: String) { /*...*/ }
主构造器初始化类的实例, 以及它在类头部中的属性. 类头部不能包含任何可执行的代码. 如果你想要在对象创建时运行某些代码, 可以使用类 body 中的 初始化代码段(initializer block) . 初始化代码段使用 init
关键字来定义, 之后是大括号. 请将你想要运行的代码放在大括号之内.
在类的实例初始化过程中, 初始化代码段按照它们在类主体中出现的顺序执行, 初始化代码段之间还可以插入属性的初始化代码:
//sampleStart
class InitOrderDemo(name: String) {
val firstProperty = "First property: $name".also(::println)
init {
println("First initializer block that prints $name")
}
val secondProperty = "Second property: ${name.length}".also(::println)
init {
println("Second initializer block that prints ${name.length}")
}
}
//sampleEnd
fun main() {
InitOrderDemo("hello")
}
主构造器的参数可以在初始化代码段中使用. 也可以在类主体定义的属性初始化代码中使用:
class Customer(name: String) {
val customerKey = name.uppercase()
}
Kotlin 有一种简洁语法, 可以通过主构造器来定义属性并初始化属性值:
class Person(val firstName: String, val lastName: String, var age: Int)
这种声明还可以包含类属性的默认值:
class Person(val firstName: String, val lastName: String, var isEmployed: Boolean = true)
声明类的属性时, 可以使用 尾随逗号(trailing comma) :
class Person(
val firstName: String,
val lastName: String,
var age: Int, // 尾随逗号(trailing comma)
) { /*...*/ }
与通常的属性一样, 主构造器中定义的属性可以是可变的(var
), 也可以是只读的(val
).
如果构造器有注解, 或者有可见度修饰符, 这时 constructor
关键字是必须的, 注解和修饰符要放在它之前:
class Customer public @Inject constructor(name: String) { /*...*/ }
详情请参见 可见度修饰符 .
次级构造器(secondary constructor) 类还可以声明 次级构造器(secondary constructor) , 使用 constructor
关键字作为前缀:
class Person(val pets: MutableList<Pet> = mutableListOf())
class Pet {
constructor(owner: Person) {
owner.pets.add(this) // 将这个 pet 实例添加到它的主人的 pet 列表
}
}
如果类有主构造器, 那么每个次级构造器都必须委托给主构造器, 要么直接委托, 要么通过其他次级构造器间接委托. 委托到同一个类的另一个构造器时, 使用 this
关键字实现:
class Person(val name: String) {
val children: MutableList<Person> = mutableListOf()
constructor(name: String, parent: Person) : this(name) {
parent.children.add(this)
}
}
初始化代码段中的代码实际上会成为主构造器的一部分. 在访问次级构造器的第一条语句时, 会执行对主构造器的委托调用, 因此所有初始化代码段中的代码, 以及属性初始化代码, 都会在次级构造器的函数体之前执行.
即使类没有定义主构造器, 也会隐含地委托调用主构造器, 因此初始化代码段仍然会被执行:
//sampleStart
class Constructors {
init {
println("Init block")
}
constructor(i: Int) {
println("Constructor $i")
}
}
//sampleEnd
fun main() {
Constructors(1)
}
如果一个非抽象类没有声明任何主构造器和次级构造器, 它将带有一个自动生成的, 无参数的主构造器. 这个构造器的可见度为 public.
如果不希望你的类带有 public 的构造器, 可以声明一个空的构造器, 并明确设置其可见度:
class DontCreateMe private constructor() { /*...*/ }
在 JVM 中, 如果主构造器的所有参数都指定了默认值, 编译器将会产生一个额外的无参数构造器, 这个无参数构造器会使用默认参数值来调用既有的构造器. 有些库(比如 Jackson 或 JPA) 会使用无参数构造器来创建对象实例, 这个特性将使得 Kotlin 比较容易与这种库协同工作.
class Customer(val customerName: String = "")
创建类的实例 要创建一个类的实例, 需要调用类的构造器, 调用方式与使用通常的函数一样:
val invoice = Invoice()
val customer = Customer("Joe Smith")
关于嵌套类, 内部类, 以及匿名内部类的实例创建过程, 请参见嵌套类(Nested Class) .
抽象类 类本身, 或类中的部分成员, 都可以声明为 abstract
的. 抽象成员在类中不存在具体的实现. 你不必对抽象类或抽象成员标注 open 修饰符.
abstract class Polygon {
abstract fun draw()
}
class Rectangle : Polygon() {
override fun draw() {
// 描绘长方形
}
}
你可以使用抽象成员来覆盖一个非抽象的 open
成员:
open class Polygon {
open fun draw() {
// 某种默认的多边形描绘方法
}
}
abstract class WildShape : Polygon() {
// 从 WildShape 继承的类需要实现自己的 draw 方法,
// 而不是使用 Polygon 中的默认方法
abstract override fun draw()
}
同伴对象(Companion Object) 如果你需要写一个函数, 希望使用者不必通过类的实例来调用它, 但又需要访问类的内部信息(比如, 一个工厂方法), 你可以将这个函数写为这个类之内的一个 对象声明 的成员, 而不是类本身的成员.
具体来说, 如果你在类中声明一个 同伴对象 , 那么只需要使用类名作为限定符就可以访问同伴对象的成员了.
最终更新: 2024/10/17