简体   繁体   中英

How to generate default values for a data class / POJO (in Kotlin/Java)?

Remark: I ask it in Kotlin, but it applies to Java too.

For example, I have a POJO / data class:

data class Example(
    val someInt: Int,
    val someString: String,
    val someObject: Any,
    ... say it has 10 fields ...
)

I need to pass it to some code in my tests, and that particular code only cares about the someString field. Therefore, I hope I do not need to care about those other fields and just fill them with garbage (possibly just things like 0 and ""). Also, I do not want to use default values for the constructor, because for normal business code that field should be required, and it is only in test code that I want to fill dummy value.

Thus, I wonder how to generate default values for a data class / POJO (in Kotlin/Java)?

My naive thoughts: Use reflection and call the constructor. But I hope there already exist a library, or there is a simpler solution. Thanks!

EDIT

About why I need this: In the old days when using Java POJO, I can solve this problem by making the Example class like

class Example {
  int someInt;
  String someString;
  ...
}

Then I use it like

new Example()
  .setSomeInt(42)
  .setSomeString("hello")
  // I do not care about all other fields in this specific test case, so I just leave them null

But in kotlin, the idiomatic approach is to define the data class as the code example at the beginning of this question. Then, we see that (1) fields are final (2) not nullable. Then the old approach cannot work.

You just have to initialize the data objects with some defaults in the setup code of you tests. One wonders why their values would not matter. Maybe this in itself is an indication that the code is not designed well.

If some fields truly don't matter for the test, just pass null or 0 or "" (of whatever simple default is applicable).

To avoid duplication you can set up a method for this that you use throughout your test.

If you use IntelliJ IDEA you may define live template for this: 在此处输入图像描述

Now, in the context of a Kotlin variable creation (actually, any expression creation) you have a smart completion for "example" abbreviation:

在此处输入图像描述

Unfortunately, it can't be limited to the scope of Tests Files (this live template will be shown for all Kotlin code).

Not sure if I understand the question correctly, but maybe this helps.

I would create a class like

public class YourClass{
int something = 0;
}

Then no arg constructor - this should construct a YourClass object with something = 0 in it.

Then constructor with (int something) - this should handle the case when the default value needs to be overwritten.

As you mention yourself reflection would be a solution. Here is a solution quickly put together to give you a rough idea:

data class Example(
  val someInt: Int,
  val someString: String,
  val someObject: Any,
  val someList: MutableList<Any>
)

fun initializeDataClass(cls: Class<*>): Any {
  val constructor = cls.constructors[0]
  val arguments = mutableListOf<Any>()
  for (type in constructor.parameterTypes) {
    arguments.add(
      when (type) {
        Boolean::class.java      -> false
        Char::class.java         -> ' '
        Byte::class.java         -> 0
        Short::class.java        -> 0
        Int::class.java          -> 0
        Long::class.java         -> 0
        Float::class.java        -> 0.0
        Double::class.java       -> 0.0
        String::class.java       -> ""
        Array::class.java        -> arrayOf<Any>()
        BooleanArray::class.java -> arrayOf<Boolean>()
        CharArray::class.java    -> arrayOf<Char>()
        ByteArray::class.java    -> arrayOf<Byte>()
        ShortArray::class.java   -> arrayOf<Short>()
        IntArray::class.java     -> arrayOf<Int>()
        LongArray::class.java    -> arrayOf<Long>()
        FloatArray::class.java   -> arrayOf<Float>()
        DoubleArray::class.java  -> arrayOf<Double>()
        List::class.java         -> listOf<Any>()
        Map::class.java          -> mapOf<Any, Any>()
        Set::class.java          -> setOf<Any>()
        else                     -> Any()
      }
    )
  }
  return constructor.newInstance(*arguments.toTypedArray())
}

val example = (initializeDataClass(Example::class.java) as Example)
  .copy(someString = "abc", someList = mutableListOf(1, 2))

The last line contains three steps:

  • creating the instance (with initializeDataClass)
  • casting it to Example
  • create a copy and set the desired fields to values which matter for the test

My naive solution based on reflection:

usage:

fake<Book>().copy(name="hello")

code:

package com.cjy.yplusplus.util

inline fun <reified T : Any> fake() = Fakes.fakeImpl(T::class.java) as T

object Fakes {
    fun <T : Any> fakeImpl(clazz: Class<T>) = createFakeObject(clazz)

    private val INFOS = listOf(
        Info(listOf(Int::class.java, java.lang.Integer::class.java)) { 0 },
        Info(listOf(Long::class.java, java.lang.Long::class.java)) { 0L },
        Info(listOf(Double::class.java, java.lang.Double::class.java)) { 0.0 },
        Info(listOf(Float::class.java, java.lang.Float::class.java)) { 0.0f },
        Info(listOf(String::class.java, java.lang.String::class.java)) { "" },
        Info(listOf(List::class.java, MutableList::class.java, java.util.List::class.java)) { ArrayList<Any?>() },
        Info(listOf(Map::class.java, MutableMap::class.java, java.util.Map::class.java)) { HashMap<Any?, Any?>() },
    )

    private fun createFakeObject(type: Class<*>): Any {
        println("createFakeObject type=$type")

        // special check, notice it is "==" not "isAssignableFrom"
        if (type == Any::class.java || type == Object::class.java) {
            return Object()
        }

        return INFOS
            .firstOrNull { info -> info.classes.any { infoClazz -> infoClazz.isAssignableFrom(type) } }
            ?.defaultValue?.invoke()
            ?: createFakeObjectByConstructor(type)
    }

    private fun createFakeObjectByConstructor(clazz: Class<*>): Any {
        println("createFakeObjectByConstructor clazz=$clazz")
        val ctor = clazz.constructors.first()
        val args = ctor.parameterTypes.map { createFakeObject(it) }.toTypedArray()
        println("createFakeObjectByConstructor ctor=$ctor args=$args")
        return ctor.newInstance(*args)
    }

    private data class Info(
        val classes: List<Class<*>>,
        val defaultValue: () -> Any,
    )
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM