【问题标题】:Kotlin - generate toString() for a non-data classKotlin - 为非数据类生成 toString()
【发布时间】:2017-04-13 05:34:24
【问题描述】:

情况:

我有一个带有lateinit 字段的类,所以它们不存在于构造函数中:

class ConfirmRequest() {
    lateinit var playerId: String
}

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

问题:

有没有办法在 Kotlin 中实现它?

【问题讨论】:

  • Lombok 可能仍然有效,不是吗?
  • Lombok 注释不起作用,解释:stackoverflow.com/questions/35517325/…
  • 我建议在同一类中挑战lateinint var 和“数据”toString() 的需求。如果不了解更多如何使用ConfirmRequest,很难做出推荐,但data class ConfirmRequest(var playerId: String? = null) 对我来说似乎工作得很好。如果您知道在 playerId == null 时永远不会使用它,那么您可以将数据成员设为私有,并为方便起见公开一个公共的非空属性。
  • 这个类是从 JSON 反序列化的,需要有空的构造函数
  • 根据所使用的 Json 库,您实际上并不总是需要默认构造函数,例如 Jackson 不需要它。

标签: java kotlin tostring


【解决方案1】:

推荐的方式是手动写toString(或者用IDE生成),希望这样的类不要太多。

data class 的目的是容纳 85% 的最常见情况,将 15% 留给其他解决方案。

【讨论】:

  • 好的,必须用 IDE 生成它
  • 最后决定使用jackson-module-kotlin,它允许使用参数构造函数,所以数据类现在适合我的需要:data class ConfirmRequest(var playerId: String)
  • @awfun,jackson-module-kotlin 是否支持data class ConfirmRequest(val playerId: String)
【解决方案2】:

我发现带有反射功能的 Apache Commons Lang 的 ToStringBuilder 很有用,但是当我不需要它时它会调用 hashCode() 和其他方法(并且来自第 3 方库的称为 hashCode() 的方法会生成 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(", ")}]"
}

【讨论】:

  • 简单的解决方案,但当一个人不能使用数据类时就足够了
  • 工作正常,除了delegated properties - 你会得到你的字符串表示为Bean=[property1$delegate={}, property2$delegate={}, property3$delegate={}]
【解决方案3】:

和你一样,我习惯于在 Java 中将 lombok 用于 toString()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)
}

【讨论】:

  • 关于 java 的 lombok 的伟大提示,希望我早点知道:(
  • 是的,当时我专注于性能。回想起来,为了方便起见,我可能会添加它。
【解决方案4】:

这就是我最终要做的。

Any类上创建扩展函数

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]

【讨论】:

  • 尽管这很酷,但不鼓励使用反射。正如 Kotlin 文档中所述:“这称为反射,它的性能不是很好,所以除非你真的需要它,否则请避免它”(kotlinlang.org/docs/tutorials/kotlin-for-py/…
  • @traneHead 普遍同意反射缓慢。如果这不是您的用例的约束,那么这种方法是添加 toString 的快速方法。
【解决方案5】:

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

https://stackoverflow.com/a/46247234/97777

【讨论】:

    【解决方案6】:

    如何使用 Kotlin 反射?我在 Kotlin 上呆了几天,如果我误解了问题,或者写了“Kotlin 效率低下”的例子,我深表歉意。

    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 作为 fun toString2。那么所有实现 ToString2Interface 的类都会有 toString2()

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-06-14
      • 2016-02-09
      • 2017-04-15
      • 2019-04-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多