Kotlin面向对象编程

文章说明

本文内容基于Kotlin官方文档总结翻译而成,旨在为自己学习Kotlin使用。文中的代码示例、技术概念和最佳实践均参考自Kotlin官方资源和相关技术文档,如需获取最新或更详细的信息,建议查阅官方文档。


1. 类与对象

Kotlin支持面向对象编程,允许通过类和对象组织代码。类是对象的蓝图,定义对象的属性和行为。

类的定义

在Kotlin中,使用class关键字声明类:

class Person {
// 类的内容:属性和函数
}

创建对象

要创建类的实例,直接调用构造函数,无需new关键字:

val person = Person()

属性

类的属性可以在类头部的构造函数参数中声明,或在类体中定义:

class Person(val firstName: String, var age: Int) {
// 在构造函数中定义的属性

// 在类体中定义的属性
var lastName: String = ""
}

属性可以是只读(val)或可变(var):

  • val - 只读属性,初始化后不能更改
  • var - 可变属性,可以更改其值

访问属性

使用点符号(.)访问类的属性:

val person = Person("John", 25)
println(person.firstName) // 输出: John
person.age = 26 // 修改可变属性
person.lastName = "Smith" // 设置类体中定义的属性

2. 构造函数

Kotlin中的类可以有一个主构造函数和多个次构造函数。

主构造函数

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

class Person constructor(firstName: String) {
// 类体
}

如果主构造函数没有任何注解或可见性修饰符,可以省略constructor关键字:

class Person(firstName: String) {
// 类体
}

要使构造函数参数成为属性,需添加valvar修饰符:

class Person(val firstName: String, var age: Int) {
// firstName是只读属性,age是可变属性
}

初始化块

初始化块使用init关键字定义,用于放置初始化代码:

class Person(val name: String) {
val upperName: String

init {
upperName = name.uppercase()
println("Person created with name: $name")
}
}

初始化块按照它们在类中出现的顺序执行,与属性初始化交错进行。

次构造函数

次构造函数使用constructor关键字声明:

class Person(val name: String) {
var age: Int = 0

constructor(name: String, age: Int) : this(name) {
this.age = age
}
}

次构造函数必须直接或间接委托给主构造函数,使用this关键字实现委托。

3. 成员函数

类中可以定义函数来表示该类的行为:

class Person(val name: String) {
fun greet() {
println("Hello, my name is $name")
}

fun celebrateBirthday(age: Int): Int {
val newAge = age + 1
println("$name is now $newAge years old")
return newAge
}
}

调用成员函数:

val person = Person("Alice")
person.greet() // 输出: Hello, my name is Alice
val newAge = person.celebrateBirthday(25) // 输出: Alice is now 26 years old

4. 继承

Kotlin中所有类默认是final的(不可继承)。要允许类被继承,需使用open关键字:

基类声明

open class Person(val name: String) {
open fun greet() {
println("Hello, I'm $name")
}

fun introduce() {
println("My name is $name")
}
}

派生类声明

要继承一个类,使用冒号后跟基类名称:

class Student(
name: String,
val studentId: String
) : Person(name) {
override fun greet() {
println("Hi, I'm a student and my name is $name")
}
}

继承规则:

  • 必须在派生类构造函数中初始化基类
  • 要重写基类的方法,基类方法必须标记为open
  • 重写的方法必须使用override修饰符

调用超类实现

在派生类中,可以使用super关键字调用超类的方法:

class Student(name: String, val studentId: String) : Person(name) {
override fun greet() {
super.greet() // 调用Person的greet方法
println("I'm a student with ID: $studentId")
}
}

派生类初始化顺序

在构造派生类实例时,基类的初始化先于派生类进行:

  1. 基类构造函数的参数被评估
  2. 基类的初始化块和属性初始化器执行
  3. 派生类的初始化块和属性初始化器执行
  4. 派生类构造函数的代码执行

5. 接口

接口定义了类可以实现的行为契约,但不能包含状态。

接口声明

interface Drawable {
fun draw() // 抽象方法

fun displayInfo() { // 带有默认实现的方法
println("Drawing object")
}
}

实现接口

类可以实现一个或多个接口:

class Circle : Drawable {
override fun draw() {
println("Drawing a circle")
}

// 可以选择性地重写带有默认实现的方法
override fun displayInfo() {
println("This is a circle")
}
}

接口中的属性

接口可以包含抽象属性或带有访问器的属性:

interface Named {
val name: String // 抽象属性

val displayName: String // 带有默认getter的属性
get() = "Display: $name"
}

class User(override val name: String) : Named {
// 无需实现displayName,因其已有默认getter
}

解决实现冲突

当实现多个接口并遇到方法冲突时,必须明确指定使用哪一个实现:

interface A {
fun foo() { println("A") }
}

interface B {
fun foo() { println("B") }
}

class C : A, B {
override fun foo() {
super<A>.foo() // 调用A的实现
super<B>.foo() // 调用B的实现
println("C") // 自己的实现
}
}

6. 数据类

数据类专门用于保存数据,编译器会自动生成有用的方法。

声明数据类

data class User(val name: String, val age: Int)

对于每个数据类,编译器自动生成:

  • equals()/hashCode() - 比较实例的方法
  • toString() - 格式为 “User(name=John, age=42)”
  • componentN() - 用于解构声明
  • copy() - 创建实例副本的方法

使用数据类

val user1 = User("John", 30)
val user2 = User("John", 30)

println(user1 == user2) // true:自动实现equals()
println(user1.toString()) // User(name=John, age=30)

// 解构声明
val (name, age) = user1
println("$name is $age years old") // John is 30 years old

// 复制对象并修改部分属性
val olderUser = user1.copy(age = 31)
println(olderUser) // User(name=John, age=31)

数据类要求

数据类必须满足以下要求:

  • 主构造函数至少有一个参数
  • 所有主构造函数参数必须标记为valvar
  • 数据类不能是抽象的、开放的、密封的或内部的

7. 密封类与接口

密封类提供了对类层次结构的受控继承,所有直接子类在编译时都是已知的。

声明密封类

sealed class Result {
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
object Loading : Result()
}

使用密封类

密封类与when表达式结合使用时特别有用,可以进行详尽的条件检查:

fun handleResult(result: Result) = when(result) {
is Result.Success -> "Success with data: ${result.data}"
is Result.Error -> "Error: ${result.message}"
is Result.Loading -> "Loading..."
// 不需要else分支,因为所有情况都已覆盖
}

密封类规则

  • 密封类的直接子类必须在同一个包和模块中声明
  • 子类可以是普通类、数据类或对象
  • 子类可以有自己的子类,这些子类可以在任何地方声明

8. 对象表达式与对象声明

Kotlin提供了创建匿名类实例和实现单例模式的便捷方法。

对象表达式(匿名内部类)

对象表达式创建匿名类的实例:

interface OnClickListener {
fun onClick()
}

val button = Button()
button.setOnClickListener(object : OnClickListener {
override fun onClick() {
println("Button clicked")
}
})

匿名对象可以继承多个类或实现多个接口。

对象声明(单例)

对象声明定义了单例类:

object Logger {
fun log(message: String) {
println("LOG: $message")
}
}

// 使用
Logger.log("This is a log message")

对象声明不能有构造函数,但可以有初始化块。

伴生对象

伴生对象提供了与Java静态成员类似的功能:

class MyClass {
companion object {
const val TAG = "MyClass"

fun create(): MyClass {
return MyClass()
}
}
}

// 使用
val tag = MyClass.TAG
val instance = MyClass.create()

伴生对象可以实现接口或继承类,也可以拥有自己的名称。

参考链接