Подтвердить что ты не робот

Kotlin генерирует конструктор, который устанавливает значения по умолчанию для нулевых аргументов

Возьмем класс класса данных:

data class User(
    val userNumber: Int = -1,
    val name: String,
    val userGroups; List<String> = emptyList(),
    val screenName: String = "new-user"
)

При вызове этой функции из Kotlin это довольно просто. Для этого я могу просто использовать синтаксис named-argument. Вызов из Java, я должен указать все значения или использовать аннотацию @JvmOverloads, которая генерирует следующие конструкторы (в дополнение к конструктору, который kotlin генерирует с битовой маской для значений по умолчанию):

User(int userNumber, @NotNull String name, @NotNull List userGroups,
     @NotNull String screenName)
User(int userNumber, @NotNull String name, @NotNull List userGroups)
User(int userNumber, @NotNull String name)
User(@NotNull String name)

Теперь, если я хочу создать объект User в Java, эквивалентный User(name="John Doe", userGroups=listOf("admin", "super"), я не могу сделать это с помощью указанных выше конструкторов. Я могу, однако, сделать это, если я положил val userNumber: Int = -1 в конце объявления data class (генерация конструкторов, похоже, зависит от порядка, в котором определены дополнительные аргументы). Это хорошо, потому что ожидая, что котлин будет генерировать все перестановки, будет сильно раздуваться несколько классов.

Самая большая проблема в том, что такие инструменты, как Jackson, просто не работают, поскольку они понятия не имеют, какой конструктор использовать (а не как я могу аннотировать один из сгенерированных специально).

Итак, есть способ генерации (одиночного) конструктора типа:

User(Integer userNumber, String name, List<String> userGroups, String screenName) {
    this.userNumber = (userNumber == null) ? -1 : userNumber;
    this.userGroups = (userGroups == null) ? Collections.emptyList() : userGroups;
    //...
}

В настоящее время я использую вышеупомянутый подход, но вручную определяю конструкторы, в которых они мне нужны.

ИЗМЕНИТЬ

Я должен уточнить, создание аналогичного конструктора не работает, очевидно, потому что обе подписи будут сталкиваться с JVM. Это то, что он хотел бы в моем случае:

data class User(
    val userNumber: Int = -1,
    val name: String,
    val userGroups; List<String> = emptyList(),
    val screenName: String = "new-user"
) {
    companion object {
        @JvmStatic
        @JsonCreator
        fun constructionSupport(
            @JsonProperty("userNumber") userNumber : Int?,
            @JsonProperty("name") name : String,
            @JsonProperty("userGroups") userGroups : List<String>?,
            @JsonProperty("screenName") screenName : String?
        ) = User(
            userNumber = userNumber ?: -1,
            name = name,
            userGroups = userGroups ?: emptyList(),
            screenName = screenName ?: "new-user"
        )
    }
}

Также обратите внимание на избыточность, когда я должен дважды писать значения по умолчанию для свойств. Теперь, когда я смотрю на это, я сомневаюсь, что для этого существует решение. Возможно, это хороший прецедент для моего бокового проекта kapt:)

4b9b3361

Ответ 1

Лучшее решение - добавить библиотеку к пониманию функциональности Котлина. Например, для Джексона существует jackson-module-kotlin. С помощью этой библиотеки мы можем использовать аргументы по умолчанию в классах данных.

Пример:

data class User(
        val userNumber: Int = -1,
        val name: String,
        val userGroups: List<String> = emptyList(),
        val screenName: String = "new-user"
)

fun main(args: Array<String>) {
    val objectMapper = ObjectMapper()
            .registerModule(KotlinModule())

    val testUser = User(userNumber = 5, name = "someName")

    val stringUser = objectMapper.writeValueAsString(testUser)
    println(stringUser)

    val parsedUser = objectMapper.readValue<User>(stringUser)
    println(parsedUser)

    assert(testUser == parsedUser) {
        println("something goes wrong")
    }
}

Ответ 2

После того, как вы обманули это в течение минуты, я думаю, что нашел решение, которое может работать здесь хорошо. Просто определите функцию верхнего уровня в том же исходном файле, который будет строить объект. Возможно, так:

fun build_user(userNumber: Int?, name: String, userGroups: List<String>?, screenName: String?) : User {
  return User(if(userNumber !== null) userNumber else -1, name, if(userGroups !== null) userGroups else emptyList(),
    if(screenName !== null) screenName else "new-user")
}

Затем, когда вам это нужно, вы просто вызываете его из Java:

User user = UserKt.build_user(null, "Hello", null, "Porterhouse Steak");
System.out.println(user);

Вывод из примера:

User(userNumber=-1, name=Hello, userGroups=[], screenName=Porterhouse Steak)

Метод находится где-то между конструктором и строителем. Он бьет, выбивая полномасштабный объект Builder, и избегает загромождать ваш data class ненужной беспорядочной связью кода Java-interop.

Подробнее см. Функции уровня пакета.