Как создать экземпляр класса параметризованного типа по имени

Следующий пример:

У меня есть сервис, который может запустить любой автомобиль

interface VehicleStarterService<T: Vehicle> {
   fun <T : Vehicle> start(vehicle: Class<T>): String {
       return vehicle.start
   }
}

Я хотел бы создать тип транспортного средства с именем, например «Автомобиль», для чего мне потребуется создать VehicleStarterService; однако я не могу найти способ создать экземпляр интерфейса с классом, который создается по имени.

Я хотел бы сделать что-то вроде (но не могу):

val cls = "Car"
val kClass = Class.forName(cls).kotlin
val service = VehicleStarterService<kClass>()
service.start

В итоге мне нужно сделать следующее (создать службу для каждого параметризованного типа, который мне нужен):

class CarStarterService : VehicleStarterService<Car> {
    fun <T> execute() : String {
        return start(User::class.java)
    }
}

Есть ли способ таким образом создать экземпляр параметризованного класса?

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

Michael Butscher 13.09.2018 21:06

То, что вы собираетесь сделать, эквивалентно необработанному типу, поэтому просто используйте необработанный тип.

Louis Wasserman 16.09.2018 00:16
0
2
168
2

Ответы 2

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

val cls = "Car"
val service: VehicleStarterService<out Vehicle>? = when (cls) {
    "Car"  -> object : VehicleStarterService<Car> {}
    "Boat" -> object : VehicleStarterService<Boat> {}
    "Plane" -> object : VehicleStarterService<Plane> {}
    else   -> null
}
service?.start(...

Обновлено: идея реестра hashmap для обеспечения некоторой расширяемости ..

val serviceRegistry = HashMap<String, VehicleStarterService<out Vehicle>>()
    .apply {
        //default services
        this["Car"] = object : VehicleStarterService<Car> {}
        this["Boat"] = object: VehicleStarterService<Boat> {}
    }
.
.
.
//Adding entry
serviceRegistry["Plane"] = object: VehicleStarterService<Plane> {}
.
.
.
//Fetching entry
val cls = "Car"
val service = serviceRegistry[cls]

service?.start(...

Я надеялся, что смогу избежать добавления подобного сопоставителя (требующего от кого-то другого изменить базовый класс «библиотеки»), но да, это определенно вариант!

LostHawkGSW 14.09.2018 00:56

Я отредактировал вторую идею, используя хэш-карту в качестве реестра, чтобы кто-то другой мог добавлять свои собственные службы и получать их.

luminous_arbour 14.09.2018 07:02

Я действительно не вижу твоей проблемы. Обратите внимание, что информация об общем типе стирается во время выполнения (по крайней мере, для варианта JVM, который вы, очевидно, используете). Таким образом, в принципе не имеет значения, какой типизированный сервис вы используете. Вы также можете просто использовать VehicleStarterService<Vehicle>, и он запустит все ваши автомобили. Если по какой-либо причине вам нужно вызвать конкретную (другую!) Функцию, вам все равно нужно проверить / привести к вашему фактическому типу ... Так что я не вижу никакой выгоды в попытке получить этот общий типизированный интерфейс.

Сказав, что все еще есть способы сделать это ... (обратите внимание: на самом деле это не имеет значения ... информация об общем типе стирается во время выполнения ...)

Теперь для своих тестов я использую этот метод ввода:

fun startAllTheVehicles(vararg vehicleTypes: String) {
  startAllTheVehicles(*vehicleTypes.map { Class.forName(it) }
                                   .map { it as Class<out Vehicle> }
                                   .toTypedArray())
}

Обратите внимание, что it as Class<out Vehicle> НЕ гарантирует, что у вас есть класс типа Vehicle. Он просто гарантирует, что ваш код компилируется при попытке вызвать ваши функции, которые объявляют Class<out Vehicle> как тип параметра.

Итак, начиная с этого момента, вы можете использовать один из следующих способов получить доступный общий тип. Я предполагаю, что вы используете что-то вроде newInstance в своей стартовой службе.

  1. Пример с опущенным универсальным типом в интерфейсе (этот случай довольно простой):

    interface VehicleStarterService {
      fun <T: Vehicle> start(vehicleType: Class<T>) = vehicleType.newInstance().start
    }
    

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

    fun <T : Vehicle> startAllTheVehicles(vararg vehicleTypes: Class<out T>) {
      val service = object : VehicleStarterService {}
      vehicleTypes.forEach { service.start(it) }
    }
    
  2. По-прежнему используется интерфейс с универсальным типом (обратите внимание на изменения, касающиеся out T и т. д.):

    interface VehicleStarterService<T: Vehicle> {
      fun start(vehicleType: Class<out T>) = vehicleType.newInstance().start
    }
    

    Пример метода, который вызывается функцией входа с использованием одной службы для запуска всех транспортных средств:

    fun <T : Vehicle> startAllTheVehicles(vararg vehicleTypes: Class<out T>) {
      // actually it doesn't really matter which type we use...
      val service = object : VehicleStarterService<Vehicle> {} // you could even use T instead of Vehicle
      vehicleTypes.forEach { service.start(it) }
    }
    

Тестирование обоих со следующими работает, как ожидалось:

startAllTheVehicles("Car", "Truck")

Вызов его с типом, который на самом деле не является Vehicle, даст вам ClassCastException в функции интерфейса start.

Другие вопросы по теме