Kotlin 类与对象
Kotlin 在支持面向对象编程的同时引入了非常多的改进:
- 针对构造函数、属性声明等高频使用场景,提供主构造函数、数据类等更加简洁的语法
- 拥有空值安全、扩展函数、对象、委托等特性,使得开发过程更加安全与灵活
类
Kotlin 中使用 class
关键字声明类:
class Person { ... }
要创建这个类的实例,只需要像调用普通函数一样调用该类的构造函数即可:
val person = Person()
在类的定义中,可以包含:
构造函数
在 Kotlin 中,类可以有一个主构造函数和任意多个次构造函数。主构造函数是类头的一部分,直接跟在类名后面,而次构造函数则是在类体中定义的
主构造函数
class Person constructor(name: String, age: Int) {
val name: String = name
var age: Int = age
}
在以上类定义中,constructor(name: String, age: Int)
即为主构造函数,因其名称和位置固定,可以省略 constructor
关键字
主构造函数中的参数可以在类体中使用,比如 name
参数用于初始化 name
属性,当传入的参数与属性的名称、类型、初始值均相同时,可以在前面加上 val
或 var
关键字,相当于直接在类头声明了属性。如此,以上类定义可以简化为:
class Person(val name: String, var age: Int)
初始化块
由于主构造函数只能包含参数或属性声明,不能包含语句,因此如果需要在创建对象时执行一些代码,可以在类体中使用 init
初始化块,实际上,初始化块也被视为主构造函数的一部分:
class Person(val name: String, val age: Int) {
init {
println("Person created")
println("The name is $name, age is $age")
}
}
次构造函数
当需要多种实例化的方式时,可以在类体中使用 constructor
关键字声明次构造函数,次构造函数可以声明任意多个。当类拥有主构造函数时,每个次构造函数都需要直接或间接的委托到主构造函数,这可以通过使用 this
关键字实现:
class Person(val name: String) {
var age: Int? = null
init {
println("Primary constructor called with name $name")
}
// 次构造函数, 通过 this(name) 委托到主构造函数
constructor(name: String, age: Int) : this(name) {
this.age = age
println("Secondary constructor called with name $name and age $age")
}
}
fun main() {
val person1 = Person("Alice", 18)
val person2 = Person("Bob")
}
对其他构造函数的委托发生在该构造函数第一条语句执行之前,因此上述代码的输出为:
Primary constructor called with name Alice
Secondary constructor called with name Alice and age 18
Primary constructor called with name Bob
数据类
当一个类只包含表示数据的属性时,可以在 class
前添加 data
修饰符,将该类变为数据类,数据类中的主构造函数只能包含属性声明,且至少声明一个属性:
data class Person(var name: String? = null, var age: Int? = null)
数据类会自动为主构造函数中的属性生成一些标准方法:
equals()
,比较两个对象是否相等,操作符==
会调用该方法hashCode()
,返回对象的哈希值toString()
,返回Person(name=Alice, age=18)
格式的字符串componentN()
,用于解构声明中使用,如val (name, age) = person
copy()
,用于复制对象,并允许修改部分属性,如person.copy(name = "Bob")
无参构造函数
如果主构造函数中的所有参数都有默认值,编译器会为该类生成一个额外的无参构造函数
无参构造函数对于 Jackson 等库或框架的使用是必需的。如果需要无参构造函数,但不希望为每个属性都手动指定 null
的默认值,可以使用为编译器提供的 no-arg 插件,配置后编译器会为特定注解标记的 Kotlin 类生成无参构造函数
类与继承
与 Java 相反,Kotlin 中的类和函数默认都被 final
修饰,若想要允许类被继承或函数被重写,需要添加 open
修饰符:
open class Base(val name: String) {
open fun printName() {
println("The name is $name")
}
}
当继承一个类时,需要在类头的最后声明,并且提供该类构造函数所需的参数
重写函数时,需要添加 override
修饰符,且是强制性的,否则会导致编译器报错
class Derived(name: String) : Base(name) {
override fun printName() {
println("The derived name is $name")
}
}
all-open 插件
由于 Kotlin 中的类及其成员默认不被 open
修饰,使得 Spring AOP 等依赖继承和重写的框架使用起来不再方便,为了适应这些框架的需求,可以使用为编译器提供的 all-open 插件,配置后编译器会将特定注解标记的 Kotlin 类及成员标记为 open
接口与实现
Kotlin 中的接口除了可以声明抽象方法、默认方法,还可以声明抽象属性:
interface MyInterface {
val abstractProperty: Int // 抽象属性
fun abstractMethod(): String // 抽象方法
fun defaultMethod() { // 默认方法
println("This is a default method")
println("The abstractProperty value: $abstractProperty")
}
}
当实现接口时,同样需要在类头的最后声明,但无需添加代表调用构造函数的 ()
,若接口中包含抽象属性,实现类需要提供该属性的值并添加 override
修饰符:
class MyClass(override val abstractProperty: Int) : MyInterface {
override fun abstractMethod(): String {
return "This is the implementation of abstractMethod"
}
}
fun main() {
val myClass = MyClass(10)
println(myClass.abstractMethod()) // 调用抽象方法的实现
myClass.defaultMethod() // 调用默认方法
}
对象
对象声明
使用 object
关键字替换掉类声明中的 class
,即可将该类转变为一个对象,其会创建该类并在首次被访问时初始化一个该类的实例,因此对象声明是单例模式懒加载的一种实现方式
对象声明内同样可以拥有属性、方法、初始化块等,因为对象只存在一个实例,所以在使用时应当直接通过 对象名.成员名
访问:
object ConfigManager {
var environment: String = "development"
val version: String = "1.0.0"
fun printConfig() {
println("Environment: $environment, Version: $version")
}
}
fun main() {
ConfigManager.printConfig()
ConfigManager.environment = "production"
ConfigManager.printConfig()
}
伴生对象
在 Kotlin 中没有静态 static
的概念,要实现类似的功能,可以在类的内部声明一个对象作为替代,对象中的所有成员都相当于是该类的静态成员,可以通过 类名.对象名.成员名
访问:
class MyClass {
object Factory {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.Factory.create()
若想省略上述调用方式中的 对象名
,可以将该对象设置为类的伴生对象,在对象声明前添加 companion
关键字,即可直接通过 类名.成员名
访问:
class MyClass {
companion object {
fun create(): MyClass = MyClass()
}
}
val instance = MyClass.create()
对象表达式
对象表达式用于创建一个匿名类的实例,其可以从零开始定义类的结构:
val myObject = object {
val x: String = "Hello"
fun sayHello() {
println(x)
}
}
也可以继承自某个类或实现某个接口,此时需要在 object
后说明继承和实现关系,多个关系之间使用逗号分隔:
open class BaseClass {
open fun printMessage() {
println("This is BaseClass")
}
}
interface MyInterface {
fun myInterfaceMethod()
}
fun main() {
// 同时继承 BaseClass 和实现 MyInterface
val myObject = object : BaseClass(), MyInterface {
override fun printMessage() {
super.printMessage()
println("And this is overridden in myObject")
}
override fun myInterfaceMethod() {
println("MyInterfaceMethod implemented")
}
}
myObject.printMessage()
myObject.myInterfaceMethod()
}