Возможно ли в Котлине написать встроенную функцию с овеществленным типом, которая может возвращать различные типы 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
Поскольку тип элемента массива является E, вы можете проверить тип E :: class, а не тип defValue
@ErwinBolwidt Вы имеете в виду что-то вроде E :: class == Int :: class или как мне это сделать? Вы можете объяснить это поподробнее? Извините, я новичок в Kotlin и все еще думаю об ограничениях Java ...
Явно отвечая на вопрос - вы можете использовать его, проверив класс 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>
}
Но я не рекомендую его использовать, поскольку:
when
.Что происходит, когда вы его используете? Давайте проверим этот пример:
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 null
в этом случае неоднозначен, и компилятор не может определить, какой метод вы хотите вызвать. Вы должны привести его к желаемому типу, допускающему значение NULL, например getArray("...", null as Array<Int>?)
.
Если у вас ограниченное количество обрабатываемых типов, вам следует рассмотреть возможность разделения этого метода на несколько с разными типами возврата. Встраивание огромных операторов switch генерирует много мусорного байтового кода.