Scala对象不会更改其内部状态

时间:2010-01-31 20:15:02

标签: list scala state mutation

我发现我正在研究的一些Scala 2.7.7代码存在问题,如果它是用Java编写的,则不应该发生。松散地,代码会创建一堆卡片播放器并将它们分配给表格。

class Player(val playerNumber : Int)

class Table (val tableNumber : Int) {
    var players : List[Player]  = List()

    def registerPlayer(player : Player) {
        println("Registering player " + player.playerNumber + " on table " + tableNumber)
        players = player :: players
    }
}

object PlayerRegistrar  {
    def assignPlayersToTables(playSamplesToExecute : Int, playersPerTable:Int) = {
        val numTables = playSamplesToExecute / playersPerTable
        val tables = (1 to numTables).map(new Table(_))
        assert(tables.size == numTables)

        (0 until playSamplesToExecute).foreach {playSample =>
            val tableNumber : Int = playSample % numTables
            tables(tableNumber).registerPlayer(new Player(playSample))
        }
        tables
    }
}

PlayerRegistrar在表之间分配了许多玩家。首先,它计算出需要多少个表来分解玩家并创建一个列表。

然后在代码的第二部分中,它确定应该为哪个表分配一个玩家,从列表中提取该表并在该表上注册一个新玩家。

表上的播放器列表是var,并且每次调用registerPlayer()时都会被覆盖。我通过简单的TestNG测试检查了它是否正常工作:

@Test def testRegisterPlayer_multiplePlayers() {
    val table = new Table(1)
    (1 to 10).foreach { playerNumber =>
        val player = new Player(playerNumber)
        table.registerPlayer(player)
        assert(table.players.contains(player))
        assert(table.players.length == playerNumber)
    }
}

然后测试表分配:

  @Test def testAssignPlayerToTables_1table() = {
    val tables = PlayerRegistrar.assignPlayersToTables(10, 10)
    assertEquals(tables.length, 1)
    assertEquals(tables(0).players.length, 10)
}

测试失败,“预期:< 10>但是:< 0>”。我一直在挠头,但无法解决为什么registerPlayer()没有改变列表中的表。任何帮助将不胜感激。

2 个答案:

答案 0 :(得分:4)

原因是在assignPlayersToTables方法中,您正在创建一个新的Table对象。您可以通过在循环中添加一些调试来确认这一点:

val tableNumber : Int = playSample % numTables
println(tables(tableNumber))
tables(tableNumber).registerPlayer(new Player(playSample))

产生类似的东西:

Main$$anon$1$Table@5c73a7ab
Registering player 0 on table 1
Main$$anon$1$Table@21f8c6df
Registering player 1 on table 1
Main$$anon$1$Table@53c86be5
Registering player 2 on table 1

注意每次调用时表的内存地址是如何不同的。

这种行为的原因是Scala中的Range是非严格的(无论如何,直到Scala 2.8)。这意味着在需要之前不会评估对范围的调用。所以你认为你正在找回Table个对象的列表,但实际上你每次调用它时都会得到一个被评估的范围(实例化一个新的Table对象)。同样,您可以通过添加一些调试来确认:

val tables = (1 to numTables).map(new Table(_))
println(tables)

这给了你:

RangeM(Main$$anon$1$Table@5492bbba)

要做你想做的事,请在末尾添加toList

val tables = (1 to numTables).map(new Table(_)).toList

答案 1 :(得分:2)

val tables = (1 to numTables).map(new Table(_))

这一行似乎造成了所有麻烦 - 1 to n上的映射为您提供了RandomAccessSeq.Projection,说实话,我不知道它们究竟是如何工作的,但初始化不那么聪明技术完成了这项工作。

var tables: Array[Table] = new Array(numTables)
for (i <- 0 to numTables) tables(i) = new Table(i)

使用第一个初始化方法我无法更改对象(就像你一样),但是使用一个简单的数组似乎一切正常。