Reified встроенная функция для массивов

Возможно ли в Котлине написать встроенную функцию с овеществленным типом, которая может возвращать различные типы Arrays? Я думаю примерно так:

inline fun <reified E> getArray(key: String, defValue: Array<E>): Array<E>? {
    return when(defValue) {
        is Array<Int> -> // ...
        is Array<String?> -> // ...
        else // ...
    }
}

И я хотел бы назвать это так:

fun intArray(size: Int): Array<Int> = Array(size) {i -> 0}
fun stringArray(size: Int): Array<String?> = Array(size) {i -> null}

val strings: Array<Int> = getArray(KEY_INTS, intArray(0))
val strings: Array<String> = getArray(KEY_STRINGS, stringArray(0))

Но с этим я получаю ошибку:

Cannot find check for instance of erased type

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

Pawel 26.09.2018 23:43

Поскольку тип элемента массива является E, вы можете проверить тип E :: class, а не тип defValue

Erwin Bolwidt 27.09.2018 00:06

@ErwinBolwidt Вы имеете в виду что-то вроде E :: class == Int :: class или как мне это сделать? Вы можете объяснить это поподробнее? Извините, я новичок в Kotlin и все еще думаю об ограничениях Java ...

Cilenco 27.09.2018 00:21
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
3
331
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Явно отвечая на вопрос - вы можете использовать его, проверив класс E:

inline fun <reified E: Any> getArrayInline(key: String, defValue: Array<E>): Array<E>? {
    return when(E::class) {
        Int::class -> arrayOf(1, 2, 3)
        String::class -> arrayOf("a", "b", "c")
        else -> throw IllegalArgumentException("Invalid class: ${E::class.qualifiedName}")
    } as Array<E>
}

Но я не рекомендую его использовать, поскольку:

  1. Это небезопасно по типу - вы должны выполнить небезопасное приведение к результату, и его можно вызвать для любого типа массива, даже если он не включен в случаи when.
  2. он встроенный - поэтому весь этот блок кода копируется в байт-код всякий раз, когда вы используете метод (см. ниже)
  3. проверка типов выполняется во время выполнения, поэтому это снижает производительность

Что происходит, когда вы его используете? Давайте проверим этот пример:

fun testArrayInline(){
    val test = getArrayInline("key", emptyArray<Int>())
    val test2 = getArrayInline("key2", emptyArray<String>())
}

Все просто, правда? Но как только вы посмотрите на сгенерированный байт-код, это не так хорошо. Для удобства чтения это байт-код Kotlin, декомпилированный обратно в Java:

public static final void testArrayInline() {
  String var1 = "key";
  Object[] defValue$iv = new Integer[0];
  KClass var3 = Reflection.getOrCreateKotlinClass(Integer.class);
  Object var10000;
  if (Intrinsics.areEqual(var3, Reflection.getOrCreateKotlinClass(Integer.TYPE))) {
     var10000 = new Integer[]{1, 2, 3};
  } else {
     if (!Intrinsics.areEqual(var3, Reflection.getOrCreateKotlinClass(String.class))) {
        throw (Throwable)(new IllegalArgumentException("Invalid class: " + Reflection.getOrCreateKotlinClass(Integer.class).getQualifiedName()));
     }

     var10000 = new String[]{"a", "b", "c"};
  }

  Integer[] test = (Integer[])((Object[])var10000);
  String var7 = "key2";
  Object[] defValue$iv = new String[0];
  KClass var4 = Reflection.getOrCreateKotlinClass(String.class);
  if (Intrinsics.areEqual(var4, Reflection.getOrCreateKotlinClass(Integer.TYPE))) {
     var10000 = new Integer[]{1, 2, 3};
  } else {
     if (!Intrinsics.areEqual(var4, Reflection.getOrCreateKotlinClass(String.class))) {
        throw (Throwable)(new IllegalArgumentException("Invalid class: " + Reflection.getOrCreateKotlinClass(String.class).getQualifiedName()));
     }

     var10000 = new String[]{"a", "b", "c"};
  }

  String[] test2 = (String[])((Object[])var10000);
}

Это довольно много, учитывая, что функция вызывалась только дважды с двумя случаями в блоке «когда». И даже ничего полезного не делает - результат по кейсам if уже виден.


Верный способ сделать это - объявить каждый тип как отдельные не встроенные функции:

fun getArray(key: String, defValue: Array<Int>) : Array<Int>{
    return arrayOf(1, 2, 3)
}

fun getArray(key: String, defValue: Array<String>) : Array<String>{
    return arrayOf("a", "b", "c")
}

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

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

public static final void testArray() {
  String var3 = "key";
  Integer[] var4 = new Integer[0];
  getArray(var3, var4);
  var3 = "key2";
  String[] var5 = new String[0];
  getArray(var3, var5);
}

Спасибо за этот ответ. Что бы вы предложили, если я хочу сделать параметр defValue допускающим значение NULL? Если я позвоню getArray("...", null), получится overload resolution ambiguity. Неужели мне тогда действительно нужно использовать разные имена методов?

Cilenco 02.10.2018 07:01

@Cilenco null в этом случае неоднозначен, и компилятор не может определить, какой метод вы хотите вызвать. Вы должны привести его к желаемому типу, допускающему значение NULL, например getArray("...", null as Array<Int>?).

Pawel 02.10.2018 11:30

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