3.3.5 对象

原文链接: https://kotlinlang.org/docs/kotlin-tour-intermediate-objects.html

3.3.5 对象

本章将通过介绍对象声明来拓展你对类的理解。这些知识将帮助你高效地管理项目中的行为。

对象声明

在 Kotlin 中,你可以使用对象声明来声明一个只有单个实例的类。某种意义上,你是在同时声明这个类并创建唯一的实例。当你想要创建一个类,把它用作程序的唯一参照点,或用来协调整个系统中的行为时,对象声明会很有用。

提示: 只有一个易于访问的实例的类称为单例。

Kotlin 中的对象是惰性的,也就是说它们只在被访问时才创建。Kotlin 还确保所有对象都以线程安全的方式创建,因此你不必手动检查这一点。

要创建对象声明,请使用 object 关键字:

1
object DoAuth {}

在 object 的名称之后,把任何属性或成员函数写在由花括号 {} 定义的对象体中。

注意: 对象不能有构造器,因此它们不像类那样有类头。

例如,假设你想创建一个名为 DoAuth 的对象,由它负责身份验证:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
object DoAuth {
    fun takeParams(username: String, password: String) {
        println("input Auth parameters = $username:$password")
    }
}

fun main(){
    // The object is created when the takeParams() function is called
    DoAuth.takeParams("coding_ninja", "N1njaC0ding!")
    // input Auth parameters = coding_ninja:N1njaC0ding!
}

这个对象有一个名为 takeParams 的成员函数,它接受 username 和 password 两个变量作为参数,并向控制台打印一个字符串。DoAuth 对象只在第一次调用该函数时创建。

注意: 对象可以继承类和接口。例如:kotlin interface Auth { fun takeParams(username: String, password: String) } object DoAuth : Auth { override fun takeParams(username: String, password: String) { println("input Auth parameters = $username:$password") } }

数据对象

为了更便于打印对象声明的内容,Kotlin 提供了 data 对象。与你在入门导览中学过的数据类类似,数据对象会自动附带额外的成员函数:toString() 和 equals()。

与数据类不同,数据对象不会自动附带 copy() 成员函数,因为它们只有唯一的实例,无法被复制。

要创建数据对象,请使用与对象声明相同的语法,但在前面加上 data 关键字:

1
data object AppConfig {}

例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
data object AppConfig {
    var appName: String = "My Application"
    var version: String = "1.0.0"
}

fun main() {
    println(AppConfig)
    // AppConfig

    println(AppConfig.appName)
    // My Application
}

关于数据对象的更多信息,请参阅对象声明。

伴生对象

在 Kotlin 中,一个类可以拥有一个对象:伴生对象。每个类只能有一个伴生对象。伴生对象只在其所属的类第一次被引用时才创建。

伴生对象中声明的任何属性或函数都由该类的所有实例共享。

要在类中创建伴生对象,请使用与对象声明相同的语法,但在前面加上 companion 关键字:

1
companion object Bonger {}

注意: 伴生对象不一定要有名字。如果你没有定义名字,默认名称是 Companion。

要访问伴生对象的任何属性或函数,请通过类名引用它。例如:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
class BigBen {
    companion object Bonger {
        fun getBongs(nTimes: Int) {
            repeat(nTimes) { print("BONG ") }
            }
        }
    }

fun main() {
    // Companion object is created when the class is referenced for the
    // first time.
    BigBen.getBongs(12)
    // BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG BONG
}

这个示例创建了一个名为 BigBen 的类,其中包含一个名为 Bonger 的伴生对象。这个伴生对象有一个名为 getBongs() 的成员函数,它接受一个整数,并向控制台打印与该整数相同次数的 "BONG"。

在 main() 函数中,通过引用类名来调用 getBongs() 函数。伴生对象就在此刻创建。getBongs() 函数以参数 12 被调用。

更多信息请参阅对象声明。

练习

习题 1

你经营一家咖啡店,并有一套跟踪顾客订单的系统。请阅读下面的代码,补全第二个数据对象的声明,使 main() 函数中的代码能够正常运行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
interface Order {
    val orderId: String
    val customerName: String
    val orderTotal: Double
}

data object OrderOne: Order {
    override val orderId = "001"
    override val customerName = "Alice"
    override val orderTotal = 15.50
}

data object // Write your code here

fun main() {
    // Print the name of each data object
    println("Order name: $OrderOne")
    // Order name: OrderOne
    println("Order name: $OrderTwo")
    // Order name: OrderTwo

    // Check if the orders are identical
    println("Are the two orders identical? ${OrderOne == OrderTwo}")
    // Are the two orders identical? false

    if (OrderOne == OrderTwo) {
        println("The orders are identical.")
    } else {
        println("The orders are unique.")
        // The orders are unique.
    }

    println("Do the orders have the same customer name? ${OrderOne.customerName == OrderTwo.customerName}")
    // Do the orders have the same customer name? false
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
interface Order {
    val orderId: String
    val customerName: String
    val orderTotal: Double
}

data object OrderOne: Order {
    override val orderId = "001"
    override val customerName = "Alice"
    override val orderTotal = 15.50
}

data object OrderTwo: Order {
    override val orderId = "002"
    override val customerName = "Bob"
    override val orderTotal = 12.75
}

fun main() {
    // Print the name of each data object
    println("Order name: $OrderOne")
    // Order name: OrderOne
    println("Order name: $OrderTwo")
    // Order name: OrderTwo

    // Check if the orders are identical
    println("Are the two orders identical? ${OrderOne == OrderTwo}")
    // Are the two orders identical? false

    if (OrderOne == OrderTwo) {
        println("The orders are identical.")
    } else {
        println("The orders are unique.")
        // The orders are unique.
    }

    println("Do the orders have the same customer name? ${OrderOne.customerName == OrderTwo.customerName}")
    // Do the orders have the same customer name? false
}

示例解答

习题 2

创建一个继承自 Vehicle 接口的对象声明,以实现一种独特的交通工具类型:FlyingSkateboard。在你的对象中实现 name 属性和 move() 函数,使 main() 函数中的代码能够正常运行:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
interface Vehicle {
    val name: String
    fun move(): String
}

object // Write your code here

fun main() {
    println("${FlyingSkateboard.name}: ${FlyingSkateboard.move()}")
    // Flying Skateboard: Glides through the air with a hover engine
    println("${FlyingSkateboard.name}: ${FlyingSkateboard.fly()}")
    // Flying Skateboard: Woooooooo
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
interface Vehicle {
    val name: String
    fun move(): String
}

object FlyingSkateboard : Vehicle {
    override val name = "Flying Skateboard"
    override fun move() = "Glides through the air with a hover engine"

   fun fly(): String = "Woooooooo"
}

fun main() {
    println("${FlyingSkateboard.name}: ${FlyingSkateboard.move()}")
    // Flying Skateboard: Glides through the air with a hover engine
    println("${FlyingSkateboard.name}: ${FlyingSkateboard.fly()}")
    // Flying Skateboard: Woooooooo
}

示例解答

习题 3

你正在为一个应用构建用户注册模块。你希望把邮箱校验与 User 类关联起来,但又不想在邮箱地址无效时创建多余的 User 实例。

在这个练习中,只要邮箱地址同时包含 @ 和 . 就算有效。请补全这个数据类,使 main() 函数中的代码能够正常运行:

提示

在 User 类的伴生对象中添加一个邮箱校验函数,这样就可以直接在 User 上调用它。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
data class User(val name: String, val email: String) {
    // Write your code here
}

fun main() {
    val candidates = listOf(
        Pair("Alice", "alice@example.com"),
        Pair("Bob", "bob2example-com")
    )

    for ((name, email) in candidates) {
        if (User.isValidEmail(email)) {
            val user = User(name, email)
            println("Registered: ${user.name}, ${user.email}")
            // Registered: Alice, alice@example.com
        } else {
            println("Error: '${email}' is not valid. The email should contain '@' and '.'")
            // Error: 'bob2example-com' is not valid. The email should contain '@' and '.'
        }
    }
}
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
data class User(val name: String, val email: String) {
    companion object {
        fun isValidEmail(email: String): Boolean =
            email.contains('@') && email.contains('.')
    }
}

fun main() {
    val candidates = listOf(
        Pair("Alice", "alice@example.com"),
        Pair("Bob", "bob2example-com")
    )

    for ((name, email) in candidates) {
        if (User.isValidEmail(email)) {
            val user = User(name, email)
            println("Registered: ${user.name}, ${user.email}")
            // Registered: Alice, alice@example.com
        } else {
            println("Error: '${email}' is not valid. The email should contain '@' and '.'")
            // Error: 'bob2example-com' is not valid. The email should contain '@' and '.'
        }
    }
}

示例解答

提示: 作为这个练习的延伸,可以尝试把伴生对象中的函数用作工厂方法,来构造类的实例。关于这种模式的示例和更多信息,请参阅对象声明。

另请参阅

上一步

下一步