Search code examples
kotlinclassinheritanceconstructor

How to access inherited constructor parameters in Kotlin


I'm in the process of working through a kotlin example project. One of the tasks is to implement a game board class that can track coordinates.

The provided interfaces look like:

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?)

}

My implemented classes look like this:

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)

Everything compiles and runs, but I'm seeing the following warning in the IDE inside the init for SquareBoardImpl:

Accessing non-final property width in constructor

For this simple example, I know that I could simply ignore the warning, but I would like to know what the proper way is to handle this. How can I refer to 'width' inside the constructor in a safe way?


Solution

  • The problem here is that subclasses of SquareBoardImpl can override the width property. If they do, the body of the width accessor will be executed for only partially initialized supertype, which is not expected in most cases.

    From your code it doesn't seem you actually need width to remain open, so I suggest making it final:

    open class SquareBoardImpl(final override val width: Int) : SquareBoard { ... }
    
    class GameBoardImpl<T>(width: Int) : SquareBoardImpl(width), GameBoard<T> { ... }