使用 const 声明常量
本课属于 Coddy Kotlin 学习旅程的“面向对象编程”单元。第 14 课,共 57 课。
const val 声明一个编译时常量:它的值在程序编译时固定。它可以出现在文件顶层,也可以出现在 object 或 companion object 内,并且只能用于 Int、Double 和 Boolean 等基本类型,以及 String:
const val MAX_PLAYERS = 4
class Lobby {
val players = mutableListOf<String>()
fun canStart() = players.size in MIN_PLAYERS..MAX_PLAYERS
companion object {
const val MIN_PLAYERS = 2
}
}在 main 中:
val lobby = Lobby()
lobby.players += listOf("ada", "bo")
println(lobby.canStart())
println("${Lobby.MIN_PLAYERS}-$MAX_PLAYERS players")输出:
true
2-4 players常量使用大写蛇形命名法。const 比 val 的限制更严格:它的值必须无需运行任何代码就能确定,因此不能使用函数调用或列表。对象中的 val 也可以共享且只读,并且可以接受任意值:
const val GREETING = "Hello" // 一个 String 字面量
const val TIMEOUT_MS = 30 * 1000 // 由字面量计算得出
// const val START = System.nanoTime() // 错误:不是常量
// const val DAYS = listOf("Mon", "Tue") // 错误:只支持基本类型和 String
object Defaults {
val DAYS = listOf("Mon", "Tue") // 一个共享的只读 val
}伴生对象也可以保存共享的可变状态。每个实例都会读取和更改同一个属性,适合用于计数器和缓存。这个类会为每个名称保留一个实例,因此调用两次会返回同一个对象(=== 会检查两个引用是否指向同一个对象):
class Tile private constructor(val name: String) {
companion object {
private val cache = mutableMapOf<String, Tile>()
val created: Int
get() = cache.size
fun of(name: String) = cache.getOrPut(name) { Tile(name) }
}
}在 main 中:
val a = Tile.of("grass")
val b = Tile.of("grass")
Tile.of("water")
println(a === b)
println(Tile.created)输出:
true
2顶层还是伴生对象?顶层常量或函数属于它所在的文件,在包中的每个文件里都可以直接使用名称。常量如果属于某个类,就把它放在伴生对象中:调用方写 Lobby.MIN_PLAYERS,这样就能看出值来自哪里,而 private const val 则会留在该类内部。
// Limits.kt
const val MAX_UPLOAD_MB = 25 // 各处都以 MAX_UPLOAD_MB 的形式使用
// Lobby.kt
class Lobby {
companion object {
const val MIN_PLAYERS = 2 // 以 Lobby.MIN_PLAYERS 的形式使用
private const val ROOM_PREFIX = "L-" // 仅在 Lobby 内部使用
}
}挑战
简单完成类 Order(val item: String, val qty: Int)。它的伴生对象包含常量 MAX_QTY = 10 和 PREFIX = "ORD"、用于生成下一个订单编号的私有计数器、只读的共享总数 totalItems,以及已创建订单的计算值 count。init 代码块要求数量在 1 到 MAX_QTY 之间,并显示消息 quantity must be 1-10(由常量构成),然后为订单分配编号 ORD-001、ORD-002,依此类推,并将其数量加到 totalItems 中。label() 返回 ORD-001 tea x2。
提供的代码会输出 max per order: 10,然后读取行 tea 2 并输出标签或错误消息,最后输出 orders: 2, items: 5。被拒绝的订单不会占用编号。
你的代码写在 Order.kt 中。Main.kt 包含提供的输入/输出代码,不能修改。
亲手试试
fun main() {
// 已提供的输入输出代码:保持原样。
val input = generateSequence(::readLine).toList()
println("max per order: ${Order.MAX_QTY}")
for (line in input) {
val (item, qty) = line.split(" ")
try {
println(Order(item, qty.toInt()).label())
} catch (e: IllegalArgumentException) {
println(e.message)
}
}
println("orders: ${Order.count}, items: ${Order.totalItems}")
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Kotlin 在线编辑器