Создание экземпляра класса Kotlin из кода Java

У меня есть класс данных Kotlin с 15 полями. Когда я создаю экземпляр этого класса в своем коде Kotlin с помощью основного конструктора, я могу использовать такие полезные функции, как:

  • пропуск полей со значениями по умолчанию
  • использовать именованные параметры

Однако при создании экземпляра этого класса Kotlin из Java-кода я столкнулся с тем, что мне нужно указать все 15 параметров в конструкторе в правильном порядке, без возможности их назвать. Это особенно неудобно, когда этот код Java представляет собой модульный тест, где мне не очень интересно заполнять все эти поля, а только одно или два полезных для теста.

При использовании чисто Java я бы не столкнулся с этой проблемой, так как я бы использовал построитель (Lombok) для создания экземпляра объекта, имея возможность предоставлять только те поля, которые меня интересуют.

Есть ли способ смягчить эту проблему, или это цена, которую я должен заплатить за смешивание Java и Kotlin?


person Benjamin    schedule 21.08.2020    source источник
comment
Существует аннотация, которую вы можете использовать в конструкторе со стороны Kotlin @JvmOverloads для создания всех перегруженных конструкторов с параметрами по умолчанию, применяемыми при использовании класса из Java. Но вы не хотите использовать это для чего-то с 15 параметрами, у которых есть значения по умолчанию, потому что тогда вы смотрите на 2 ^ 15 конструкторов. Не уверен, что еще можно сделать.   -  person Tenfour04    schedule 21.08.2020
comment
@ Tenfour04, интересно! Просто опубликовал другое решение, но не знал о том, о котором вы упомянули. И 2^15 constructors определенно стоит подумать, сомневаюсь, что кто-то будет это делать на самом деле (зная стоимость).   -  person Jenea Vranceanu    schedule 21.08.2020
comment
Однако работает для 4 параметров (16 конструкторов, безусловно, управляемы. Я мог бы пройти весь путь до 5 или 6 параметров).   -  person Robert Harvey    schedule 21.08.2020
comment
Ой, я ошибаюсь. Он не создает все возможные комбинации переменных. Это одна перегрузка для каждого параметра по умолчанию. Для каждой более короткой перегрузки последний параметр опускается.   -  person Tenfour04    schedule 21.08.2020
comment
@ Tenfour04, эта аннотация действительно полезна, спасибо за упоминание!   -  person Benjamin    schedule 24.08.2020


Ответы (1)


Есть ли способ смягчить эту проблему...

Вроде как, но это не готовое решение. Подробнее об этом ниже.

... это цена, которую я должен заплатить за смешивание Java и Kotlin?

Нет. Если мы обратимся к этой проблеме, вы заметите, что именно Java не может предоставлять значения по умолчанию и, следовательно, неудобна при использовании с Kotlin. Строительство сложных объектов является основной причиной, по которой Шаблон построителя существует. Но Kotlin дает нам решение, позволяющее избежать большинства проблем, связанных с созданием сложных объектов, и делает шаблон построителя устаревшим.

Как с этим бороться?

Существует не одно решение, а несколько. Назову как минимум два, которые пришли мне в голову сразу:

  1. Первичный + вторичный конструктор;
  2. Заводы.

Первичный + вторичный конструктор

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

class Example(val param1: Any, val param2: Any? = null, val param3: Any? = null) {
    // In this situation we have to use different argument names
    // to make explicit use of primary constructor.
    // e.g. if we remove `param1 = ` or rename `requiredParam` to `param1`
    // we will get an error saying: "There's a cycle in the delegation calls chain" 
    constructor(requiredParam: Any) : this(param1 = requiredParam)
}

Заводы

В случае с фабриками все выглядит почти так же. Хотя это решение будет более подробным в Java, но оно устраняет необходимость использования именованных аргументов и дает нам свободу для дополнительной подготовки перед инициализация объекта. Мы даже можем сделать его асинхронным (как если бы это был сетевой вызов)!

class Example(val param1: Any, val param2: Any? = null, val param3: Any? = null) {
    object Factory {
        fun from(param1: Any): Example {
            return Example(param1)
        }
    }
}

Вывод

Нет правильного решения. Мы можем выбрать тот, который нам нравится, или, может быть, придумать новый.

  1. Primary + secondary constructor:
    • (+) less verbose when used from Java;
    • (-) при изменении вторичного конструктора вам придется обновить код Java;
    • (-) требует разных имен для аргументов конструктора.
  2. Factories:
    • (+) gives more freedom: e.g. you can calculate something within from function and it will be more readable in comparison to constructors;
    • (+) скрывает реализацию конструктора. Позже вы сможете изменить конструкторы без изменения кода Java или фабричных функций;
    • (-) будет более подробным при использовании из Java.
person Jenea Vranceanu    schedule 21.08.2020
comment
Спасибо за ваш подробный ответ, Женя, я отметил его как принятый, хотя я надеялся, что будет что-то лучше. - person Benjamin; 24.08.2020
comment
@ Бенджамин, пожалуйста. Возможно, в будущем будет лучшее решение, но пока, насколько я знаю, это то, что у нас есть. - person Jenea Vranceanu; 24.08.2020