【问题标题】:Kotlin on Android: map a cursor to a listAndroid 上的 Kotlin:将光标映射到列表
【发布时间】:2016-08-21 16:15:18
【问题描述】:

在 Kotlin 中,遍历 Android Cursor 对象并将结果放入列表的最佳方法是什么?

我的自动转换 Java:

val list = ArrayList<String>()
while (c.moveToNext()) {
    list.add(getStringFromCursor(c))
}

还有更惯用的方法吗?特别是,它可以在只读列表的单个分配中完成吗?比如……

val list = /*mystery*/.map(getStringFromCursor)

...或其他一些排列,其中列表被分配完整的形式。

【问题讨论】:

    标签: android functional-programming kotlin


    【解决方案1】:

    这就是我最后使用的,使用kotlin.sequences.generateSequence...

    val list = generateSequence { if (c.moveToNext()) c else null }
            .map { getStringFromCursor(it) }
            .toList()
    

    我的第一次尝试要短一些:

    val list = (1 .. c.count).map {
        c.moveToNext()
        getStringFromCursor(c)
    }
    

    两个版本都依赖于最初定位在第一条记录之前的游标(就像新游标一样)。如果不是这种情况,第二个会抛出,而第一个会返回一个较短的列表。

    【讨论】:

    • 嗯,第一项呢?我觉得这个会更好:fun Cursor.asSequence(): Sequence&lt;Cursor&gt; = generateSequence(seed = takeIf { it.moveToFirst() }) { takeIf { it.moveToNext() } }
    • 新光标位于第一条记录之前,因此第一个 c.moveToNext() 将移动到第一条记录。
    【解决方案2】:

    Cursors 是懒惰的,所以没有明显的map。但是Anko(JetBrains 的ANdroid KOtlin)确实有一个扩展方法可以帮助您映射Cursor

    该方法称为Cursor.parseList(parser)Here 你可以找到它的完整实现。用法应该是直截了当的。而here 是一些关于 sqlite 和使用 anko 解析游标的官方文档。

    【讨论】:

    • Anko 看起来很有用。但是,它似乎没有可以将光标对象发送到我现有的getStringFromCursor 函数的函数。它只发送列值的数组或映射。因为我希望逐渐将我的应用程序转换为 Kotlin,所以这是一个问题。 (不过,源代码向我展示了如何编写合适的扩展函数,非常感谢。)
    • 我不知道你的getStringFromCursor是什么样子的,但很可能它可以实现为:cursor.parseList { it["column_name_with_string"] } val COLUMN_INDEX = 2cursor.parseList { it[COLUMN_INDEX] }
    • 是的,Anko 方式看起来很不错,但在这种情况下,我正在重写测试代码,并且我想测试一个现有函数(它需要一个光标并为该记录返回一个字符串) . Anko 隐藏了光标对象,这意味着我不能将它传递给现有的函数:这​​意味着我必须重写这两个东西。
    • 实际上,我认为让我感到困惑的是parseList 采用解析器对象而不是函数。我可以改为执行以下操作,它使用封闭范围中的游标对象,并且(浪费地)忽略为每一行创建的列数组:c.asSequence().map { getStringFromCursor(c) }
    【解决方案3】:

    你可以使用这个扩展:

    fun <T> Cursor.toArrayList(block: (Cursor) -> T) : ArrayList<T> {
        return arrayListOf<T>().also { list ->
            if (moveToFirst()) {
                do {
                    list.add(block.invoke(this))
                } while (moveToNext())
            }
        }
    }
    

    并使用它:

    val list = cursor.toArrayList { getStringFromCursor(it) }
    

    【讨论】:

      【解决方案4】:

      您可以编写自己的 map() 来检查 isClosed() 并在遍历其所有行后安全地关闭 Cursor,如下所示:

      fun <T> Cursor.map(f: (Cursor) -> T): List<T> {
        val items = mutableListOf<T>()
        use {
          while (!it.isClosed && it.moveToNext()) {
            items += f(it)
          }
        }
        return items.toList()
      }
      

      示例用法:

      val contacts = cursor.map { it.toContact() }
      

      【讨论】:

        【解决方案5】:

        只是另一种看待它的方式

        cursor.moveToFirst()
        val list = generateSequence {
            cursor.moveToNext()
            getStringFromCursor(cursor)
        }.take(cursor.count).toList()
        

        【讨论】:

        • 通常不鼓励使用count。根据特定的数据库配置,您可能事先不知道确切的计数,或者实际计数可能比您得到的要大得多(由于缓存)。最安全的方法是使用moveToNext()方法的结果来判断是否已经到达终点。
        【解决方案6】:

        您的第一个建议是正确的。你不能映射光标。

        【讨论】:

        • 好的。我真的没想到能够直接这样做。我想知道是否有更惯用的方式(不一定使用map——我已经更新了问题以澄清)。
        • 你用map没关系。你不能在 Kotlin 中做到这一点。
        【解决方案7】:

        与 Rafols 类似,我创建了一个小函数来解决该问题,因为我不喜欢使用 Anko 从数组转换为对象。我的解决方案的优点:它会自动关闭游标,它是inline,因此它对性能的影响较小,您可以在parseRow 函数中使用列名。

        private inline fun <T> getObjectListFromCursor(cursor: Cursor, parseRow: (Cursor) -> T): List<T> {
                return cursor.run {
                    mutableListOf<T>().also { list ->
                        if (moveToFirst()) {
                            do {
                                list.add(parseRow(this))
                            } while (moveToNext())
                        }
                        close()
                    }
                }
            }
        

        你这样称呼它:

        val list = getObjectListFromCursor(c, this::getStringFromCursor)
        

        【讨论】:

          【解决方案8】:

          希望这对其他人有帮助。

                  var list = ArrayList<HashMap<String, String>>()
          
                  if (c != null)
                  {
                    c.moveToFirst()
                    do
                    {
                      val temp = HashMap<String, String>()
                      temp.put("id", c.getString(0))
                      temp.put("body", c.getString(1))
                      temp.put("address", c.getString(2))
                      list.add(temp)
                    }
                    while (c.moveToNext())
                  }
                  
          

          【讨论】:

          • "特别是,可以在只读列表的单个分配中完成吗?"您的答案没有解决这个问题,并且比自动转换的代码更复杂。
          • 我遇到了同样的问题,但我没有看过“只读列表的单一分配”,所以这个答案与你无关,但这解决了我的问题,所以我把它贴在这里它可能会对某人有所帮助,因为我搜索了很多但没有找到任何解决方案。
          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2019-01-16
          • 2021-09-20
          • 1970-01-01
          • 2022-08-22
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多