Kotlin类与对象的基本概念
一、Kotlin类的基础结构
1.1 类的定义与声明
Kotlin中类的定义非常简洁,使用class关键字即可。类的基本结构包括类名、构造函数和成员。例如:
class Person constructor(name: String) { // 主构造函数
var age: Int = 0 // 属性
val address: String = ""
init { // 初始化块
println("Person initialized with name: $name")
}
fun greet() { // 方法
println("Hello, my name is $name and I'm $age years old.")
}
}
主构造函数可以直接在类头中声明,使用constructor关键字(可省略)。参数可以是val或var,表示属性,也可以是普通参数。
类体中可以包含属性、方法、初始化块和嵌套类等。初始化块使用init关键字,用于在对象创建时执行额外的初始化逻辑。
1.2 类的属性
Kotlin中的属性分为可变属性(var)和不可变属性(val)。每个属性都有默认的getter和setter(val只有getter),也可以自定义。
class Rectangle(var width: Double, var height: Double) {
val area: Double // 只读属性
get() = width * height // 自定义getter
var perimeter: Double = 0.0
set(value) { // 自定义setter
field = value // 使用field关键字访问幕后字段
}
}
属性的幕后字段(backing field)是Kotlin自动为属性生成的存储位置,通过field关键字访问。只有在自定义访问器中需要直接操作存储值时才需要使用。
1.3 类的方法
类的方法定义与普通函数类似,但在类的上下文中。方法可以访问类的属性和其他方法。
class Calculator {
var result: Double = 0.0
fun add(num: Double): Calculator { // 返回this以便链式调用
result += num
return this
}
fun subtract(num: Double): Calculator {
result -= num
return this
}
fun getResult(): Double = result
}
方法可以使用默认参数、可变参数和命名参数,增强了API的灵活性。例如:
fun printInfo(name: String = "Unknown", age: Int = 0) {
println("Name: $name, Age: $age")
}
二、构造函数与初始化
2.1 主构造函数与次构造函数
Kotlin中的类可以有一个主构造函数和多个次构造函数。主构造函数在类头中声明,次构造函数使用constructor关键字在类体中声明。
class Person constructor(name: String) { // 主构造函数
var age: Int = 0
init {
println("Primary constructor called with name: $name")
}
constructor(name: String, age: Int) : this(name) { // 次构造函数
this.age = age
println("Secondary constructor called with age: $age")
}
}
次构造函数必须直接或间接调用主构造函数。如果类没有显式声明主构造函数,则会生成一个默认的无参主构造函数。
2.2 初始化块
初始化块使用init关键字,用于在对象创建时执行额外的初始化逻辑。初始化块可以有多个,按顺序执行。
class Example {
val firstProperty: String = "Initialized first"
init {
println("First initialization block")
}
val secondProperty: String = "Initialized second"
init {
println("Second initialization block")
}
}
初始化块的执行顺序与它们在类中出现的顺序一致,并且在主构造函数参数初始化之后执行。
2.3 属性初始化
属性可以在声明时直接初始化,也可以在初始化块或构造函数中初始化。对于val属性,必须在对象创建时初始化。
class User {
val name: String // 必须在构造函数或初始化块中初始化
var age: Int = 0 // 直接初始化
constructor(name: String) {
this.name = name // 在构造函数中初始化
}
init {
// 可以在这里进行额外的初始化
}
}
对于延迟初始化的属性,可以使用lateinit关键字,但只能用于var属性,并且不能是原生类型。
class MyClass {
lateinit var database: Database // 延迟初始化
fun initDatabase() {
database = Database()
}
fun useDatabase() {
if (::database.isInitialized) { // 检查是否已初始化
database.query()
}
}
}
三、继承与多态
3.1 类的继承
Kotlin中所有类默认都是final的,不能被继承。要允许类被继承,需要使用open关键字。
open class Animal(val name: String) { // 可继承的类
open fun sound() { // 可重写的方法
println("Animal makes a sound")
}
}
class Dog(name: String) : Animal(name) { // 继承自Animal
override fun sound() { // 重写方法
println("Dog barks")
}
}
子类使用冒号(:)后跟父类构造函数调用来继承父类。重写父类的方法或属性需要使用override关键字,并且被重写的成员必须是open的。
3.2 抽象类与接口
Kotlin支持抽象类和接口,用于定义抽象行为和多重实现。
抽象类使用abstract关键字声明,包含至少一个抽象成员。抽象类不能被实例化,只能被继承。
abstract class Shape {
abstract fun area(): Double // 抽象方法
fun printInfo() { // 具体方法
println("Shape with area: ${area()}")
}
}
class Circle(val radius: Double) : Shape() {
override fun area(): Double = Math.PI * radius * radius
}
接口使用interface关键字声明,可以包含抽象方法和默认实现。
interface Flyable {
fun fly() // 抽象方法
fun describe() { // 默认实现
println("This is a flyable object")
}
}
class Bird : Flyable {
override fun fly() {
println("Bird flies with wings")
}
}
3.3 多态与方法重写
Kotlin通过方法重写实现多态。子类可以重写父类的方法,提供不同的实现。
open class Vehicle {
open fun drive() {
println("Vehicle is driving")
}
}
class Car : Vehicle() {
override fun drive() {
println("Car is driving on the road")
}
}
class Boat : Vehicle() {
override fun drive() {
println("Boat is sailing on the water")
}
}
fun main() {
val vehicles: List<Vehicle> = listOf(Car(), Boat())
vehicles.forEach { it.drive() } // 多态调用
}
输出结果:
Car is driving on the road
Boat is sailing on the water
四、数据类与密封类
4.1 数据类
Kotlin的数据类用于存储数据,编译器会自动为其生成equals()、hashCode()、toString()和copy()等方法。
data class User(val name: String, var age: Int) // 数据类
fun main() {
val user1 = User("Alice", 30)
val user2 = User("Alice", 30)
println(user1 == user2) // true,equals()方法被自动生成
println(user1) // User(name=Alice, age=30),toString()方法被自动生成
val user3 = user1.copy(age = 31) // copy()方法被自动生成
println(user3) // User(name=Alice, age=31)
}
数据类必须满足以下条件:
- 主构造函数至少有一个参数
- 所有主构造函数参数必须标记为
val或var - 数据类不能是抽象、开放、密封或内部的
- 数据类只能实现接口
4.2 密封类
密封类用于表示受限的类层次结构,所有子类必须在同一个文件中声明。密封类本身是抽象的,不能直接实例化。
sealed class Result // 密封类
data class Success(val data: String) : Result() // 子类
data class Error(val message: String) : Result() // 子类
fun handleResult(result: Result) {
when (result) { // when表达式覆盖所有可能的子类
is Success -> println("Success: ${result.data}")
is Error -> println("Error: ${result.message}")
}
}
密封类的主要优点是在when表达式中不需要else分支,因为编译器可以确保所有可能的子类都被处理。
五、嵌套类与内部类
5.1 嵌套类
Kotlin中的嵌套类是在另一个类中声明的类,默认情况下它是静态的,不持有外部类的引用。
class Outer {
private val value = 42
class Nested { // 嵌套类
fun printValue() {
// 不能直接访问Outer的成员
println("Nested class")
}
}
}
fun main() {
val nested = Outer.Nested() // 直接创建嵌套类实例
nested.printValue()
}
5.2 内部类
如果需要嵌套类持有外部类的引用,可以使用inner关键字声明内部类。
class Outer {
private val value = 42
inner class Inner { // 内部类
fun printValue() {
println("Outer value: $value") // 可以访问Outer的成员
println("Outer instance: ${this@Outer}") // 引用外部实例
}
}
}
fun main() {
val outer = Outer()
val inner = outer.Inner() // 必须通过外部类实例创建内部类实例
inner.printValue()
}
内部类会隐式持有外部类的引用,这可能会导致内存泄漏,因此在使用内部类时需要谨慎。
六、对象表达式与对象声明
6.1 对象表达式
对象表达式用于创建匿名对象,类似于Java中的匿名内部类。
interface ClickListener {
fun onClick()
}
class Button {
private var listener: ClickListener? = null
fun setOnClickListener(listener: ClickListener) {
this.listener = listener
}
fun click() {
listener?.onClick()
}
}
fun main() {
val button = Button()
button.setOnClickListener(object : ClickListener { // 对象表达式
override fun onClick() {
println("Button clicked")
}
})
button.click() // 输出: Button clicked
}
对象表达式可以实现一个或多个接口,也可以继承自一个类。
6.2 对象声明
对象声明用于创建单例对象,使用object关键字。
object Settings { // 对象声明
var theme: String = "light"
var fontSize: Int = 14
fun load() {
// 从文件或偏好设置加载配置
println("Settings loaded")
}
}
fun main() {
Settings.load() // 直接访问单例对象
Settings.theme = "dark"
println("Theme: ${Settings.theme}")
}
对象声明在第一次访问时被初始化,并且是线程安全的。
七、委托模式与委托属性
7.1 委托模式
Kotlin通过by关键字支持委托模式,允许类将某些功能委托给其他对象。
interface Base {
fun print()
}
class BaseImpl(val x: Int) : Base {
override fun print() {
println(x)
}
}
class Derived(base: Base) : Base by base // 委托给base对象
fun main() {
val base = BaseImpl(10)
val derived = Derived(base)
derived.print() // 输出: 10
}
在这个例子中,Derived类将Base接口的实现委托给了base对象。
7.2 委托属性
委托属性允许将属性的getter和setter委托给另一个对象。
class Example {
var p: String by Delegate() // 委托属性
}
class Delegate {
operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
return "$thisRef, thank you for delegating '${property.name}' to me!"
}
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
println("$value has been assigned to '${property.name}' in $thisRef.")
}
}
Kotlin标准库提供了几种有用的委托属性:
- 延迟属性(lazy):第一次访问时初始化
val lazyValue: String by lazy {
println("computed!")
"Hello"
}
- 可观察属性(Observable):监听属性变化
var name: String by Delegates.observable("初始值") {
property, oldValue, newValue ->
println("${property.name}从$oldValue变为$newValue")
}
- 非空属性(NotNull):确保属性在使用前被初始化
var address: String by Delegates.notNull<String>()
八、枚举类
8.1 基本枚举
Kotlin的枚举类使用enum关键字声明,每个枚举常量都是一个对象。
enum class Color {
RED, GREEN, BLUE
}
枚举常量可以有属性和方法:
enum class Color(val rgb: Int) {
RED(0xFF0000),
GREEN(0x00FF00),
BLUE(0x0000FF);
fun getHexString(): String {
return "#${rgb.toString(16).padStart(6, '0').uppercase()}"
}
}
8.2 枚举与接口
枚举类可以实现接口:
interface Printable {
fun print()
}
enum class Status : Printable {
READY {
override fun print() {
println("Ready")
}
},
RUNNING {
override fun print() {
println("Running")
}
},
FINISHED {
override fun print() {
println("Finished")
}
}
}
8.3 枚举与密封类
枚举类和密封类都用于表示受限的类层次结构,但有一些区别:
- 枚举类的每个常量是单例,而密封类的子类可以有多个实例
- 枚举类的常量是静态已知的,而密封类的子类可以在运行时创建
- 枚举类更适合表示固定数量的常量值,而密封类更适合表示类型层次结构
九、类的可见性修饰符
9.1 可见性修饰符概述
Kotlin提供了四种可见性修饰符:public、private、protected和internal。
public(默认):所有地方可见private:只在声明的类或文件内部可见protected:只在声明的类及其子类内部可见internal:模块内部可见
9.2 类和成员的可见性
类和成员的可见性规则如下:
class Outer {
private val privateField = 1 // 只能在Outer内部访问
protected val protectedField = 2 // 能在Outer及其子类中访问
internal val internalField = 3 // 能在同一模块中访问
public val publicField = 4 // 所有地方都能访问
private fun privateMethod() {}
protected fun protectedMethod() {}
internal fun internalMethod() {}
public fun publicMethod() {}
}
9.3 构造函数的可见性
构造函数的可见性可以通过constructor关键字指定:
class Example private constructor(val value: Int) { // 私有构造函数
companion object {
fun create(): Example {
return Example(42)
}
}
}
在这个例子中,Example类的构造函数是私有的,只能通过伴生对象的create()方法创建实例。
十、伴生对象
10.1 伴生对象的定义
Kotlin中的伴生对象使用companion object声明,它与类关联,但独立于类的实例。
class MyClass {
companion object Factory { // 伴生对象,名为Factory
fun create(): MyClass {
return MyClass()
}
}
}
// 使用伴生对象
val instance = MyClass.Factory.create()
// 可以省略伴生对象的名称
val instance2 = MyClass.create()
10.2 伴生对象的特性
伴生对象的特性包括:
- 伴生对象的成员类似于Java中的静态成员,但在运行时仍然是类的实例的成员
- 伴生对象可以实现接口
- 伴生对象可以有扩展函数和属性
- 伴生对象的成员可以被继承
10.3 伴生对象与静态成员
Kotlin没有静态成员的概念,但可以通过以下方式实现类似功能:
- 伴生对象
- 顶层函数和属性
@JvmStatic注解(用于生成真正的Java静态方法)
class MyClass {
companion object {
@JvmStatic
fun staticMethod() {} // 生成Java静态方法
fun nonStaticMethod() {} // 生成实例方法
}
}
十一、类的扩展
11.1 扩展函数
Kotlin允许为现有类添加新的函数,称为扩展函数。
fun String.lastChar(): Char = this[this.length - 1] // 扩展String类
fun main() {
val str = "Hello"
println(str.lastChar()) // 输出: o
}
扩展函数不会修改原始类,而是通过静态解析调用。扩展函数可以访问类的public和protected成员,但不能访问private成员。
11.2 扩展属性
除了扩展函数,Kotlin还支持扩展属性。
val String.lastIndex: Int
get() = this.length - 1 // 扩展属性
fun main() {
val str = "Hello"
println(str.lastIndex) // 输出: 4
}
扩展属性必须定义getter(var属性还需要定义setter),因为它们没有幕后字段。
11.3 扩展的作用域
扩展可以在顶层定义,也可以在类或包内部定义。在类内部定义的扩展可以访问该类的成员。
class Host {
fun hostMethod() = "Host method"
}
class Container {
fun Host.anotherMethod() = hostMethod() + " and another method" // 类内部的扩展
fun call(host: Host) {
println(host.anotherMethod())
}
}
十二、类的序列化
12.1 实现Serializable接口
Kotlin类可以通过实现Serializable接口来支持序列化。
import java.io.Serializable
data class User(val name: String, val age: Int) : Serializable // 实现Serializable接口
fun main() {
val user = User("Alice", 30)
// 序列化
val outputStream = java.io.ObjectOutputStream(java.io.FileOutputStream("user.dat"))
outputStream.writeObject(user)
outputStream.close()
// 反序列化
val inputStream = java.io.ObjectInputStream(java.io.FileInputStream("user.dat"))
val loadedUser = inputStream.readObject() as User
inputStream.close()
println(loadedUser) // 输出: User(name=Alice, age=30)
}
12.2 使用@Transient注解
如果某些属性不需要序列化,可以使用@Transient注解。
data class User(val name: String, val age: Int) : Serializable {
@Transient
private val tempData: String = "This data won't be serialized"
}
12.3 自定义序列化方法
可以通过实现writeObject和readObject方法来自定义序列化过程。
data class User(val name: String, var age: Int) : Serializable {
@Transient
private var secret: String = "Default secret"
private fun writeObject(out: java.io.ObjectOutputStream) {
out.defaultWriteObject()
out.writeObject("Encrypted $secret") // 自定义序列化逻辑
}
private fun readObject(in: java.io.ObjectInputStream) {
in.defaultReadObject()
secret = in.readObject() as String // 自定义反序列化逻辑
}
}
十三、类的反射
13.1 获取类引用
在Kotlin中,可以通过::class语法获取类引用。
val stringClass = String::class // 获取String类的引用
fun main() {
val str = "Hello"
val runtimeClass = str::class // 获取对象的类引用
println(runtimeClass.simpleName) // 输出: String
}
13.2 使用KClass
Kotlin的类引用是KClass类型,可以通过它访问类的属性和方法。
class MyClass(val name: String, var age: Int) {
fun greet() {
println("Hello, $name!")
}
}
fun main() {
val kClass = MyClass::class
// 获取构造函数
val constructor = kClass.constructors.first()
val instance = constructor.call("Alice", 30)
// 获取属性
val nameProperty = kClass.memberProperties.find { it.name == "name" }
println(nameProperty?.get(instance)) // 输出: Alice
// 获取方法
val greetMethod = kClass.members.find { it.name == "greet" }
(greetMethod as? KFunction<*>)?.call(instance) // 输出: Hello, Alice!
}
13.3 反射的性能考虑
反射会带来一定的性能开销,因为它需要在运行时进行类型检查和方法调用。在性能敏感的代码中,应谨慎使用反射。
可以通过以下方式优化反射性能:
- 缓存反射对象,避免重复获取
- 使用Kotlin的反射API而不是Java的反射API,因为Kotlin的反射API更高效
- 在性能关键的代码中避免使用反射
十四、类的注解
14.1 注解的定义
Kotlin中的注解使用annotation关键字定义。
annotation class MyAnnotation(val value: String) // 简单注解
// 带参数的注解
annotation class Permission(
val name: String,
val level: Int = 1
)
14.2 注解的使用
注解可以应用于类、属性、方法等。
@MyAnnotation("Test")
class MyClass {
@Permission(name = "read", level = 2)
fun readData() {}
@Permission(name = "write")
fun writeData() {}
}
14.3 元注解
Kotlin提供了几种元注解来控制注解的行为:
@Target:指定注解可以应用的目标类型@Retention:指定注解的保留策略(源代码、字节码或运行时)@Repeatable:允许同一目标使用多次相同的注解@MustBeDocumented:指定注解应该包含在API文档中
@Target(AnnotationTarget.CLASS, AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
@MustBeDocumented
annotation class ApiVersion(val major: Int, val minor: Int)
十五、类的设计模式应用
15.1 单例模式
Kotlin中实现单例模式非常简单,使用对象声明即可。
object Singleton {
fun doSomething() {
println("Singleton doing something")
}
}
fun main() {
Singleton.doSomething() // 直接访问单例
}
15.2 工厂模式
工厂模式可以通过伴生对象或工厂类实现。
interface Shape {
fun draw()
}
class Circle : Shape {
override fun draw() {
println("Drawing a circle")
}
}
class Square : Shape {
override fun draw() {
println("Drawing a square")
}
}
// 工厂类
object ShapeFactory {
fun createShape(type: String): Shape {
return when (type.lowercase()) {
"circle" -> Circle()
"square" -> Square()
else -> throw IllegalArgumentException("Unknown shape type: $type")
}
}
}
15.3 装饰器模式
装饰器模式可以通过委托实现。
interface Component {
fun operation(): String
}
class ConcreteComponent : Component {
override fun operation(): String = "ConcreteComponent"
}
class Decorator(private val component: Component) : Component by component {
override fun operation(): String = "Decorator(${component.operation()})"
}
fun main() {
val component = ConcreteComponent()
val decorated = Decorator(component)
println(decorated.operation()) // 输出: Decorator(ConcreteComponent)
}
十六、Kotlin类与Java类的互操作性
16.1 Kotlin类在Java中的使用
Kotlin类可以很容易地在Java中使用。例如:
// Kotlin类
class User(val name: String, var age: Int) {
fun greet() {
println("Hello, $name!")
}
}
在Java中使用:
// Java代码
public class Main {
public static void main(String[] args) {
User user = new User("Alice", 30);
System.out.println(user.getName()); // 访问val属性的getter
user.setAge(31); // 访问var属性的setter
user.greet(); // 调用方法
}
}
16.2 Java类在Kotlin中的使用
Java类也可以在Kotlin中无缝使用。例如:
// Java类
public class Point {
private int x;
private int y;
public Point(int x, int y) {
this.x = x;
this.y = y;
}
public int getX() { return x; }
public int getY() { return y; }
public void setX(int x) { this.x = x; }
public void setY(int y) { this.y = y; }
}
在Kotlin中使用:
// Kotlin代码
fun main() {
val point = Point(10, 20)
println(point.x) // 直接访问Java的getter
point.y = 30 // 直接访问Java的setter
}
16.3 互操作性注意事项
在Kotlin与Java互操作时,需要注意以下几点:
- Kotlin的
null安全特性在Java中不生效,Java代码可以传递null给Kotlin的非空类型 - Kotlin的
Int与Java的int和Integer有细微差别 - Kotlin的默认参数在Java中需要显式传递所有参数
- Kotlin的
Unit类型对应Java的void
十七、Kotlin类的最佳实践
17.1 优先使用数据类
当类主要用于存储数据时,优先使用数据类,它会自动生成equals()、hashCode()、toString()和copy()方法。
data class Person(val name: String, var age: Int)
17.2 使用不可变数据
尽量使用val和不可变数据结构,减少副作用和并发问题。
val immutableList: List<String> = listOf("a", "b", "c") // 不可变列表
17.3 合理使用委托
使用委托模式和委托属性可以减少样板代码,提高代码复用性。
class Example {
val lazyValue: String by lazy { "Computed once" } // 延迟初始化
}
17.4 避免过度继承
优先使用组合而非继承,减少类之间的耦合。
// 组合而非继承
class Car(private val engine: Engine) {
fun start() {
engine.start()
}
}
17.5 使用密封类处理有限的类型层次
当需要表示有限的类型层次时,使用密封类可以获得编译时的安全性。
sealed class Result
data class Success(val data: String) : Result()
data class Error(val message: String) : Result()
fun handleResult(result: Result) {
when (result) {
is Success -> println("Success: ${result.data}")
is Error -> println("Error: ${result.message}")
}
}
十八、Kotlin类的常见误区与陷阱
18.1 混淆内部类与嵌套类
内部类(使用inner关键字)持有外部类的引用,而嵌套类不持有。混淆这两者可能导致内存泄漏。
class Outer {
inner class Inner { // 持有Outer的引用
fun doSomething() {
// 可以访问Outer的成员
}
}
class Nested { // 不持有Outer的引用
fun doSomething() {
// 不能访问Outer的成员
}
}
}
18.2 过度使用反射
反射会带来性能开销,并且使代码更脆弱。应尽量避免在性能敏感的代码中使用反射。
18.3 忽略空安全
虽然Kotlin提供了空安全特性,但在与Java互操作时,仍需注意Java代码可能传递null。
val javaObject: JavaClass = getJavaObject() // JavaClass可能返回null
javaObject.doSomething() // 可能导致NullPointerException
18.4 错误使用继承
过度使用继承会导致类层次结构复杂,增加维护难度。应优先使用组合和接口实现代码复用。
十九、Kotlin类的性能考虑
19.1 值类(Value Classes)
Kotlin 1.5引入了值类(inline class),它可以避免不必要的装箱和拆箱操作,提高性能。
@JvmInline
value class Password(private val value: String) {
fun isValid() = value.length >= 8
}
19.2 对象声明与单例性能
对象声明(单例)在第一次访问时初始化,并且是线程安全的,性能开销很小。
object Configuration {
val settings = loadSettings() // 第一次访问时初始化
}
19.3 扩展函数的性能
扩展函数是静态解析的,不会带来额外的运行时开销,性能与普通函数相当。
19.4 数据类的性能
数据类的自动生成方法(如equals()和hashCode())经过优化,性能良好。
二十、Kotlin类的未来发展
20.1 语言特性的演进
Kotlin团队持续改进语言特性,未来可能会引入更多与类和对象相关的功能,如更强大的委托机制、改进的类型系统等。
20.2 与其他技术的集成
Kotlin将进一步加强与其他技术的集成,如Kotlin/Native、Kotlin/JS等,为类和对象的跨平台使用提供更好的支持。
20.3 工具链的完善
Kotlin的开发工具链(如IDE支持、调试工具等)将不断完善,提高开发效率和体验。
通过深入理解Kotlin类与对象的基本概念和底层原理,开发者可以更好地利用Kotlin的特性,编写出更高效、更安全、更易维护的代码。
2060

被折叠的 条评论
为什么被折叠?



