MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

Kotlin类与对象基础

2023-06-246.7k 阅读

Kotlin类的基本定义

在Kotlin中,类是构建面向对象编程的基础单元。定义一个类非常简单,使用class关键字,后面跟着类名和可选的类体。例如:

class Person

上述代码定义了一个名为Person的类,这个类目前没有任何属性和方法,是一个非常简单的空类。

通常情况下,我们会给类添加属性和方法,以使其具有实际的功能。属性用于存储对象的状态,方法用于定义对象可以执行的操作。以下是一个带有属性和方法的类示例:

class Rectangle {
    var width = 0
    var height = 0

    fun area(): Int {
        return width * height
    }
}

在这个Rectangle类中,我们定义了两个可变属性widthheight,用于表示矩形的宽和高,它们的初始值都为0。同时,我们定义了一个area方法,该方法用于计算并返回矩形的面积。

类的属性

可变属性与不可变属性

在Kotlin中,属性分为可变属性(var)和不可变属性(val)。可变属性可以在初始化后重新赋值,而不可变属性在初始化后其值不能再改变。

class Circle {
    val radius: Double
    var area: Double

    init {
        radius = 5.0
        area = Math.PI * radius * radius
    }
}

在上述Circle类中,radius是一个不可变属性,一旦在init块中初始化后,就不能再改变其值。area是一个可变属性,虽然在init块中进行了初始化,但后续可以根据需要重新赋值。

属性的访问器

Kotlin为属性提供了自动生成的访问器(gettersetter)。对于不可变属性(val),只有getter;对于可变属性(var),有gettersetter。我们也可以自定义访问器的实现。

class Temperature {
    var celsius: Double = 0.0
        get() = field
        set(value) {
            field = if (value > 100) 100.0 else if (value < -273.15) -273.15 else value
        }
}

Temperature类中,我们自定义了celsius属性的setter。这里使用了field关键字,它指代属性的幕后字段,用于实际存储属性的值。通过自定义setter,我们确保celsius的值在合理的范围内。

类的构造函数

主构造函数

Kotlin中的类可以有一个主构造函数和多个次构造函数。主构造函数是类头的一部分,紧跟在类名之后。

class User constructor(username: String, password: String) {
    var username: String
    var password: String

    init {
        this.username = username
        this.password = password
    }
}

在上述User类中,constructor(username: String, password: String)是主构造函数。主构造函数中的参数可以直接作为属性使用,也可以像这里一样在init块中进行赋值。

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

class Point(x: Int, y: Int) {
    var x: Int
    var y: Int

    init {
        this.x = x
        this.y = y
    }
}

次构造函数

类还可以有多个次构造函数,次构造函数使用constructor关键字定义。

class Employee {
    var name: String
    var age: Int
    var salary: Double

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

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

Employee类中,我们定义了两个构造函数。第一个是主构造函数,接收三个参数并初始化属性。第二个是次构造函数,它接收两个参数,并通过this关键字调用主构造函数,为salary属性设置默认值0.0。

对象的创建与初始化

创建对象

创建对象非常简单,使用类名加括号的形式,传递构造函数所需的参数。

val rectangle = Rectangle()
rectangle.width = 10
rectangle.height = 5
val area = rectangle.area()

上述代码首先创建了一个Rectangle对象,然后设置其widthheight属性,最后调用area方法计算矩形的面积。

对象的初始化顺序

当一个对象被创建时,Kotlin会按照以下顺序进行初始化:

  1. 主构造函数的参数被解析。
  2. 初始化块(init块)按照它们在类中出现的顺序执行。
  3. 次构造函数的代码执行(如果有次构造函数)。

例如:

class InitOrderExample {
    val firstProperty: String = "First property: ${initializer()}"

    init {
        println("First initializer block that prints ${firstProperty}")
    }

    val secondProperty: String = "Second property: ${initializer()}"

    init {
        println("Second initializer block that prints ${secondProperty}")
    }

    constructor() {
        println("Constructor that prints ${firstProperty} and ${secondProperty}")
    }

    private fun initializer(): String {
        val result = "Initialization value"
        println("Initializing property: $result")
        return result
    }
}

在这个例子中,firstPropertysecondProperty在初始化块之前初始化,初始化块在构造函数之前执行。通过initializer函数的打印,可以清楚地看到初始化的顺序。

类的继承

声明基类与派生类

在Kotlin中,所有类都继承自Any类,Any类是所有类的超类。要声明一个类继承自另一个类,使用冒号(:)。

open class Shape {
    open fun draw() {
        println("Drawing a shape")
    }
}

class Rectangle : Shape() {
    override fun draw() {
        println("Drawing a rectangle")
    }
}

在上述代码中,Shape类被声明为open,表示它可以被继承。Rectangle类继承自Shape类,并通过override关键字重写了draw方法。

重写方法

当一个类继承自另一个类并想要重写其方法时,必须使用override关键字。同时,被重写的方法在基类中必须被声明为open

open class Animal {
    open fun makeSound() {
        println("The animal makes a sound")
    }
}

class Dog : Animal() {
    override fun makeSound() {
        println("The dog barks")
    }
}

在这个例子中,Dog类重写了Animal类的makeSound方法,提供了符合狗这个特定动物的声音实现。

调用超类方法

在重写的方法中,可以通过super关键字调用超类的方法。

open class Vehicle {
    open fun start() {
        println("Vehicle is starting")
    }
}

class Car : Vehicle() {
    override fun start() {
        super.start()
        println("Car is starting")
    }
}

Car类的start方法中,首先调用了超类Vehiclestart方法,然后再打印特定于汽车启动的信息。

可见性修饰符

类与成员的可见性

Kotlin提供了四种可见性修饰符:publicprivateprotectedinternal

  1. public:默认的可见性修饰符,所有位置都可以访问。
class PublicClass {
    public var publicProperty: String = "Public property"
    public fun publicMethod() {
        println("This is a public method")
    }
}
  1. private:仅在声明它的类内部可见。
class PrivateClass {
    private var privateProperty: String = "Private property"
    private fun privateMethod() {
        println("This is a private method")
    }
}
  1. protected:在声明它的类及其子类内部可见。
open class ProtectedClass {
    protected var protectedProperty: String = "Protected property"
    protected fun protectedMethod() {
        println("This is a protected method")
    }
}

class SubProtectedClass : ProtectedClass() {
    fun accessProtected() {
        protectedMethod()
        println(protectedProperty)
    }
}
  1. internal:在同一个模块内可见。模块是一组一起编译的Kotlin文件,例如一个Gradle或Maven项目。
internal class InternalClass {
    internal var internalProperty: String = "Internal property"
    internal fun internalMethod() {
        println("This is an internal method")
    }
}

数据类

数据类的定义与特点

数据类是一种特殊的类,主要用于保存数据。Kotlin通过自动生成一些常用的方法,如equalshashCodetoStringcopy等,来简化数据类的使用。

data class UserData(val username: String, val age: Int)

上述代码定义了一个UserData数据类,它有两个属性usernameage。Kotlin会自动为这个数据类生成以下方法:

  1. equals:基于属性值比较两个对象是否相等。
  2. hashCode:根据属性值生成哈希码。
  3. toString:返回包含所有属性值的字符串表示。
  4. copy:创建一个新的对象,其属性值与原对象相同,但可以选择性地修改某些属性值。

使用数据类的copy方法

copy方法非常有用,当我们需要创建一个与现有对象类似但某些属性值不同的新对象时,可以使用它。

val user1 = UserData("John", 30)
val user2 = user1.copy(age = 31)

在上述代码中,user2是通过user1调用copy方法创建的,user2usernameuser1相同,而age被修改为31。

密封类

密封类的定义与用途

密封类用于表示受限的类继承结构,即一个密封类的所有子类都必须在与密封类自身相同的文件中声明。密封类通常用于实现具有有限数量选项的状态机或类型检查。

sealed class Result
class Success : Result()
class Failure : Result()

在上述代码中,Result是一个密封类,SuccessFailure是它的子类。由于Result是密封类,我们可以在when表达式中安全地对其进行类型检查,而不需要添加else分支。

fun handleResult(result: Result) {
    when (result) {
        is Success -> println("Operation successful")
        is Failure -> println("Operation failed")
    }
}

密封类与枚举类的区别

虽然密封类和枚举类都可以用于表示有限数量的选项,但它们有一些重要的区别:

  1. 枚举类:枚举常量是单例的,且通常用于表示一组固定的常量值。枚举类不能有状态(即属性),除非这些属性是val且在构造函数中初始化。
  2. 密封类:密封类的子类可以有自己的状态和行为。密封类更适合表示具有不同类型的有限选项,且每个选项可能有不同的属性和行为。

嵌套类与内部类

嵌套类

嵌套类是定义在另一个类内部的类。嵌套类与外部类没有特殊的关联,它不能访问外部类的非静态成员。

class Outer {
    class Nested {
        fun nestedMethod() {
            println("This is a nested class method")
        }
    }
}

要创建嵌套类的对象,需要使用外部类名作为前缀:

val nested = Outer.Nested()
nested.nestedMethod()

内部类

内部类使用inner关键字定义,它可以访问外部类的所有成员,包括私有成员。

class OuterInner {
    private val outerProperty = "Outer property"

    inner class Inner {
        fun innerMethod() {
            println("Accessing outer property: $outerProperty")
        }
    }
}

创建内部类的对象需要通过外部类的实例:

val outer = OuterInner()
val inner = outer.Inner()
inner.innerMethod()

伴生对象

伴生对象的定义与使用

在Kotlin中,类可以有一个伴生对象,使用companion object关键字定义。伴生对象类似于Java中的静态成员,它提供了一种在类中定义与类相关但不依赖于类实例的方法和属性的方式。

class MathUtils {
    companion object {
        fun add(a: Int, b: Int): Int {
            return a + b
        }
    }
}

要调用伴生对象中的方法,不需要创建类的实例:

val result = MathUtils.add(3, 5)

伴生对象的属性

伴生对象也可以有属性,这些属性同样可以通过类名直接访问。

class Constants {
    companion object {
        const val PI = 3.14159
        val version = "1.0"
    }
}
println(Constants.PI)
println(Constants.version)

在上述代码中,PI是一个常量属性,version是一个可变属性,它们都可以通过Constants类名直接访问。

通过以上对Kotlin类与对象基础的详细介绍,你应该对Kotlin在面向对象编程方面的特性有了较为深入的理解。从类的基本定义、属性和构造函数,到继承、可见性修饰符等,Kotlin提供了丰富且强大的功能,帮助开发者构建健壮、高效的应用程序。无论是小型项目还是大型企业级应用,掌握这些基础知识都是非常重要的。