Kotlin动态字符串模板

xienkqul  于 2023-05-18  发布在  Kotlin
关注(0)|答案(3)|浏览(130)

我想知道是否有一种方法来创建动态字符串模板。它是一个字符串,当它的参数改变时,它的值会改变。

val param = mutableListOf("a")
val paramString = "${param[0]}"
print(paramString)
param[0] = "b"
print(paramString)
// this prints `aa`, I want to get `ab`

简单的String.replace()是行不通的,因为初始任务是为给定表的列表计算相同的长SQL查询。在这个sql查询中,还有其他模板参数可能包含不可预测的符号,这些符号可能会被调用String.replace()替换。
我目前得到的最好的想法是将查询拆分为两个字符串startend,然后执行get查询字符串,如下所示:

val tables = listOf("employees", "customers") // tables for query
val start = "SELECT * FROM "
val end = """// some big query here
...
"""
for(table in tables) {
    val query = start + table + end
    // do something with query
}

但我认为动态模板在更复杂的情况下可能会很有用,这不是那么简单就能解决的。

tf7tbtn2

tf7tbtn21#

到目前为止给出的所有答案都是100%正确的,但是既然你有只引用val而不调用函数的想法,我想这可能是一个有用的补充,提到property delegation的可能性。你可以提出一个像这样的委托人:

class StringTemplateDelegate(private val producer: () -> String) {
    private var fixedString: String? = null
    operator fun getValue(thisRef: Any?, property: KProperty<*>): String {
        return fixedString ?: producer()
    }
    
    operator fun setValue(thisRef: Any?, property: KProperty<*>, value: String) {
        fixedString = value
    }
}

fun dynamicTemplate(producer: () -> String) = StringTemplateDelegate(producer)

然后像这样使用它:

val param = mutableListOf("a")
val paramString by dynamicTemplate { "${param[0]}" }
print(paramString) // prints 'a'
param[0] = "b"
print(paramString) // prints 'b'

here是KotlinPlayground上可运行的示例,供您尝试或编辑。
备注:
1.虽然我将委托命名为' StringTemplateDelegate '和提供程序函数' dynamicTemplate ',但是这个实现并不特定于字符串甚至字符串模板。将String类型替换为泛型,您就有了一个委托,每当访问提供程序函数的值时,它就执行该函数。
1.我对委托进行了编码,以便它在技术上也可以用作var,如果您要重新分配它,它将只具有您重新分配给的常量值。这是一个值得怀疑的功能,但它演示了委托如何也可以用于var

hgqdbh6s

hgqdbh6s2#

这个想法很有趣,但这不是我所知道的任何语言中的语言特征。关键的问题是程序如何决定备份信息是否已更改,因此必须重新计算,除非您希望在每次访问时计算它。
如果你希望每次访问都更新它,那么你要寻找的是一个函数,因为Strings是不可变的,因此在创建后不能更改。因此,除非你改变paramString引用,否则它下面的值将始终保持不变。(你可以在这方面作弊,但我强烈反对这样做,因为它会有意想不到的副作用,并且不是在任何情况下都可靠地工作)
但是,您可以创建一个DynamicStringTemplate类来实现这一点,但是,每次访问都必须重新计算。我曾经为JUnit测试构建过类似的东西,在那里我需要动态构建Strings来进行比较,但是我使用双问号作为占位符,并根据索引从列表中动态插入值。这是一个有点不那么漂亮,你正在寻找的,但会做的工作,在大多数简单的情况下。
你也可以这样做,尽管遗憾的是,它没有附带你喜欢的花哨模板语法:

class DynamicStringTemplate private constructor() {
    private val elements = ArrayList<Any>()
    override fun toString(): String = get()

    fun get(): String {
        val state = StringBuilder()
        for (element in elements) {
            if (element is Function0<*>) {
                state.append(element.invoke())
            } else {
                state.append(element)
            }
        }
        return state.toString()
    }

    fun append(element: Any): DynamicStringTemplate {
        elements += element
        return this
    }

    operator fun plus(element: Any): DynamicStringTemplate = append(element)
    operator fun invoke(): String = get()

    companion object {
        fun dynTemplate() = DynamicStringTemplate()
        fun dynTemplate(builder: () -> String): DynamicStringTemplate = dynTemplate(builder as Any)
        fun dynTemplate(vararg element: Any): DynamicStringTemplate {
            val template = DynamicStringTemplate()
            template.elements.addAll(element)
            return template
        }
    }
}

下面是一些测试用例来展示它是如何工作的:

@Test
fun test_MonolithicLambdaBuilder() {
    val list = arrayListOf(1, "b", null)
    val template = dynTemplate { "My list contains $list." }
    assertEquals(template(), "My list contains [1, b, null].")
    list[2] = "42"
    assertEquals(template(), "My list contains [1, b, 42].")
}

// This might be the most interesting case for you...
@Test
fun test_NoValueCaptureInKotlin() {
    var a = "a"
    val template = dynTemplate { "My value is $a." }
    assertEquals(template(), "My value is a.")
    a = "b"
    assertEquals(template(), "My value is b.")
}

@Test
fun test_ChainBuilder() {
    val list = arrayListOf(1, "b", null)
    val template = dynTemplate("My list contains ") + list + "."
    assertEquals(template(), "My list contains [1, b, null].")
    list[2] = "42"
    assertEquals(template(), "My list contains [1, b, 42].")
}

@Test
fun test_VarargBuilder() {
    val list = arrayListOf(1, "b", null)
    val template = dynTemplate("My list contains ", list, ".")
    assertEquals(template(), "My list contains [1, b, null].")
    list[2] = "42"
    assertEquals(template(), "My list contains [1, b, 42].")
}

@Test
fun test_Callbacks() {
    var counter = 0
    val template = dynTemplate("This template was called ", { ++counter }, " times.")
    assertEquals(template(), "This template was called 1 times.")
    assertEquals(template(), "This template was called 2 times.")
    assertEquals(template(), "This template was called 3 times.")
}

@Test
fun test_MonolithicCallback() {
    var counter = 0
    val template = dynTemplate { "This template was called ${ ++counter } times." }
    assertEquals(template(), "This template was called 1 times.")
    assertEquals(template(), "This template was called 2 times.")
    assertEquals(template(), "This template was called 3 times.")
}

这还有一个额外的好处,就是您可以通过将模板对象传递给其他函数(如配置求值器)来操作模板对象。

z9zf31ra

z9zf31ra3#

val paramString = "${param[0]}"
paramString是不可变的变量,没有办法改变它。
也许,你可以尝试function way做你想做的事情。
就像这样:

fun main() {
    var subject = "world"
    val hello = {"hello $subject"}
    println(hello())

    subject = "stackoverflow"
    println(hello())
}

/* Output: 
hello world
hello stackoverflow
*/

希望能帮上忙。

相关问题