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

后端 未结 6 942
终归单人心
终归单人心 2020-12-17 08:17

Situation:

I have a class with lateinit fields, so they are not present in the constructor:

class ConfirmRequest() {
           


        
相关标签:
6条回答
  • 2020-12-17 08:21

    Like you, I was used to using lombok for toString() and equals() in Java, so was a bit disappointed that non-data classes in Kotlin required all of the standard boilerplate.

    So I created Kassava, an open source library that lets you implement toString() and equals() without any boilerplate - just supply the list of properties and you're done!

    e.g.

    // 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)
    }
    
    0 讨论(0)
  • 2020-12-17 08:21

    The recommended way is to write toString manually (or generate by IDE) and hope that you don't have too many of such classes.

    The purpose of data class is to accommodate the most common cases of 85%, which leaves 15% to other solutions.

    0 讨论(0)
  • 2020-12-17 08:22

    This is what I endup doing.

    Create extension function on 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}]"
    }
    

    Then you can call this method from individual type.

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

    It generates string below

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

    With name field masked:

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

    It generates,

    Table [colums=[], name=****, schema=pg_catalog, type=SYSTEM INDEX]
    
    0 讨论(0)
  • 2020-12-17 08:23

    I find Apache Commons Lang's ToStringBuilder with reflection useful, but it calls hashCode() and other methods when I don't need that (and one called hashCode() from a 3rd-party lib generates an NPE).

    So I just go with:

    // 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(", ")}]"
    }
    
    0 讨论(0)
  • 2020-12-17 08:32

    What about using Kotlin reflection? I am into Kotlin for a few days, so apologies, if I misunderstood question, or wrote "Kotlin inefficient" example.

    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
    }
    

    This can also could be implemented in newly created interface for example ToString2Interface as fun toString2. Then all classes which implements ToString2Interface would have toString2()

    0 讨论(0)
  • 2020-12-17 08:36

    You can define a data class that contains the data that you want to use and implement methods by delegating to that.

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

    0 讨论(0)
提交回复
热议问题