我正在处理一个 kotlin 示例项目。其中一项任务是实现一个可以跟踪坐标的游戏板类。
提供的接口如下所示:
interface SquareBoard {
val width: Int
fun getCellOrNull(i: Int, j: Int): Cell?
fun getCell(i: Int, j: Int): Cell
fun getAllCells(): Collection<Cell>
}
interface GameBoard<T> : SquareBoard {
operator fun get(cell: Cell): T?
operator fun set(cell: Cell, value: T?)
}
我实现的类如下所示:
open class SquareBoardImpl( override val width: Int) : SquareBoard {
private val cells:List<Cell>
init {
val coords = (1..width).toList()
fun pairs(i: Int) = coords.withIndex().map { j -> Cell(i, j.value) }
this.cells = coords.flatMap(::pairs)
}
override fun getCellOrNull(i: Int, j: Int): Cell?
{
return cells.find { cell-> cell == Cell(i,j) }
}
override fun getCell(i: Int, j: Int): Cell {
return getCellOrNull(i, j) ?: throw IllegalArgumentException()
}
override fun getAllCells(): Collection<Cell>
{
return cells;
}
}
class GameBoardImpl<T>(override val width: Int) : SquareBoardImpl(width), GameBoard<T>
{
var cellMap:MutableMap<Cell, T?> = getAllCells().associateWith { null }.toMutableMap()
override fun get(cell: Cell): T? {
return cellMap[cell]
}
override fun set(cell: Cell, value: T?) {
cellMap[cell] = value
}
}
fun createSquareBoard(width: Int): SquareBoard = SquareBoardImpl(width)
fun <T> createGameBoard(width: Int): GameBoard<T> = GameBoardImpl<T>(width)
一切都编译并运行,但我在 SquareBoardImpl 的 init 内的 IDE 中看到以下警告:
在构造函数中访问非最终属性宽度
对于这个简单的例子,我知道我可以简单地忽略警告,但我想知道处理这个问题的正确方法是什么。如何以安全的方式在构造函数中引用“宽度”?
“在构造函数中访问非最终属性宽度”警告与您在
SquareBoardImpl
类的 init 块中使用 width 属性这一事实有关,并且它未标记为 final
。在 Kotlin 中,访问构造函数中的非最终属性可能会导致问题,因为子类可能会覆盖该属性,并且当超类构造函数运行时子类实现可能尚未准备好。
要解决此警告,您可以将
cells
字段的初始化移至惰性委托。在这种情况下,cells
字段的初始化不会在构造函数中完成,而是在第一次访问时完成:
private val cells: List<Cell> by lazy {
val coords = (1..width).toList()
fun pairs(i: Int) = coords.withIndex().map { j -> Cell(i, j.value) }
coords.flatMap(::pairs)
}
这里的问题是
SquareBoardImpl
的子类可以覆盖 width
属性。如果这样做,将仅针对部分初始化的超类型执行 width
访问器的主体,这在大多数情况下是不期望的。
从您的代码看来,您实际上并不需要
width
保持开放,所以我建议将其定为最终版本:
open class SquareBoardImpl(final override val width: Int) : SquareBoard { ... }
class GameBoardImpl<T>(width: Int) : SquareBoardImpl(width), GameBoard<T> { ... }