Kotlin - 为非数据生成 toString() class

Kotlin - generate toString() for a non-data class

情况:

我有一个带有 lateinit 字段的 class,因此它们不存在于构造函数中:

class ConfirmRequest() {
    lateinit var playerId: String
}

我想要一个包含所有字段的 toString() 方法,不想手动编写它,以避免重复打印。在 Java 中,我将使用 Lombok @ToString 注释来解决这个问题。

问题:

有什么方法可以在 Kotlin 中实现吗?

推荐的方式是手动写toString(或者IDE生成)希望大家不要有太多这样的类.

data class 的目的是为了适应 85% 的最常见情况,剩下 15% 给其他解决方案。

像你一样,我习惯在 toString() 和 Java 中使用 lombok equals(),所以有点失望的是 Kotlin 中需要非数据 类所有标准样板文件。

所以我创建了 Kassava,一个开源库,让您无需任何样板即可实现 toString()equals() - 只需提供属性列表即可!

例如

// 1. Import extension functions
import au.com.console.kassava.kotlinEquals
import au.com.console.kassava.kotlinToString

import java.util.Objects

class Employee(val name: String, val age: Int? = null) {

    // 2. Optionally define your properties for equals()/toString() in a  companion
    //    object (Kotlin will generate less KProperty classes, and you won't have
    //    array creation for every method call)
    companion object {
        private val properties = arrayOf(Employee::name, Employee::age)
    }

    // 3. Implement equals() by supplying the list of properties to be included
    override fun equals(other: Any?) = kotlinEquals(
        other = other, 
        properties = properties
    )

    // 4. Implement toString() by supplying the list of properties to be included
    override fun toString() = kotlinToString(properties = properties)

    // 5. Implement hashCode() because you're awesome and know what you're doing ;)
    override fun hashCode() = Objects.hash(name, age)
}

我发现 Apache Commons Lang 的 ToStringBuilder with reflection 很有用,但它会在我不需要时调用 hashCode() 和其他方法(还有一个调用 hashCode() 来自 3rd- party lib 生成一个 NPE)。

所以我选择了:

// class myClass
    override fun toString() = MiscUtils.reflectionToString(this)

// class MiscUTils
fun reflectionToString(obj: Any): String {
    val s = LinkedList<String>()
    var clazz: Class<in Any>? = obj.javaClass
    while (clazz != null) {
        for (prop in clazz.declaredFields.filterNot { Modifier.isStatic(it.modifiers) }) {
            prop.isAccessible = true
            s += "${prop.name}=" + prop.get(obj)?.toString()?.trim()
        }
        clazz = clazz.superclass
    }
    return "${obj.javaClass.simpleName}=[${s.joinToString(", ")}]"
}

您可以定义一个数据 class,其中包含您要使用的数据,并通过委托给它来实现方法。

使用 Kotlin 反射怎么样?我进入 Kotlin 几天了,很抱歉,如果我误解了问题,或者写了 "Kotlin inefficient" 示例。

override fun toString() : String{
    var ret : String = ""
    for (memberProperty in this.javaClass.kotlin.memberProperties){
        ret += ("Property:${memberProperty.name} value:${memberProperty.get(this).toString()}\n");
    }
    return ret
}

这也可以在新创建的接口中实现,例如 ToString2Interface as fun toString2。那么所有实现 ToString2Interface 的 类 都将具有 toString2()

这就是我最终要做的。

Any class

上创建扩展函数
fun Any.toStringByReflection(exclude: List<String> = listOf(), mask: List<String> = listOf()): String {
    val propsString = this::class.memberProperties
            .filter { exclude.isEmpty() || !exclude.contains(it.name) }
            .joinToString(", ") {
                val value = if (!mask.isEmpty() && mask.contains(it.name)) "****" else it.getter.call(this).toString()
                "${it.name}=${value}"
            };

    return "${this::class.simpleName} [${propsString}]"
}

然后你可以从单个类型调用这个方法。

override fun toString(): String {
    return this.toStringByReflection()
}

生成下面的字符串

Table [colums=[], name=pg_aggregate_fnoid_index, schema=pg_catalog, type=SYSTEM INDEX]

名称字段被屏蔽:

override fun toString(): String {
    return this.toStringByReflection(mask= listOf("name"))
}

它生成,

Table [colums=[], name=****, schema=pg_catalog, type=SYSTEM INDEX]