Kotlin中如何安全访问构造函数内的非final属性width?
问题描述
我正在开发一个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提示警告:
Accessing non-final property width in constructor
我不想忽略警告,想了解如何在构造函数内安全引用width的正确方式。
解决方案
这个警告的核心原因是:SquareBoardImpl是open类,它的width属性默认是可被子类重写的(Kotlin中open类的属性若未显式用final修饰,就属于非final属性)。在构造函数或init块中访问这类属性,存在子类重写后返回异常值的风险——比如子类重写width时返回和构造传入不同的值,会导致父类初始化逻辑出错。
以下是几种可靠的解决方式:
方法一:标记width为final
如果不需要子类重写width,直接给属性加上final修饰符,消除非final属性的风险:
open class SquareBoardImpl(final override val width: Int) : SquareBoard { // 其余代码保持不变 }
方法二:用构造参数暂存值,避免直接访问属性
如果需要保留width的可重写性,可在构造函数中新增一个局部参数,用它来完成init块中的初始化逻辑,而非直接访问width属性:
open class SquareBoardImpl(private val initialWidth: Int) : SquareBoard { override val width: Int = initialWidth private val cells: List<Cell> init { val coords = (1..initialWidth).toList() fun pairs(i: Int) = coords.withIndex().map { j -> Cell(i, j.value) } this.cells = coords.flatMap(::pairs) } // 其余代码保持不变 } // 子类调用时正常传入width参数即可 class GameBoardImpl<T>(override val width: Int) : SquareBoardImpl(width), GameBoard<T> { // 其余代码保持不变 }
方法三:将SquareBoardImpl改为非open类
如果这个类不需要被继承,直接去掉open修饰符,此时width会默认成为final属性,警告自动消失:
class SquareBoardImpl(override val width: Int) : SquareBoard { // 其余代码保持不变 }
从你的代码场景来看,方法一或方法三最为适用——因为GameBoardImpl只是继承父类但并未重写width,将width设为final或者把父类改为非open,都能安全解决警告问题。
内容的提问来源于stack exchange,提问作者pbuchheit
相关产品推荐
相关产品推荐

