You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 00:54:56