Comments 19
name?.length
? Не склонен считать авторов Kotlin такими невежами, выбрасывающими каждый раз NPE — но хотелось бы знать, что же в итоге if (name != null) {
name.length()
}
Попробовал скомпилировать такую функцию:
fun test(name: String?) {
println(name?.length);
}
Кодогенератор до сих пор как-то хромает. Получается что-то в духе:
Object tmp$ = name == null ? null: Integer.valueOf(name.length);
nop;
System.out.println(tmp$);
Вроде можно было и без временной переменной обойтись. А самое главное — непонятно, зачем nop. Не следует думать, что они безобидны. Легко можно превысить какой-нибудь эвристический предел в JIT-компиляторе (например, на длину байткода метода) и лишним нопом отключить какую-нибудь оптимизацию.
Никто не мешает перегрузить значение из кучи в локальную переменную или на стек однократно. А локальные переменные и стек в Java не могут измениться из других потоков — это закон. Так что, думаю, здесь всё хорошо.
Вот тут много: https://www.youtube.com/watch?v=yYG12qaxWO4
interface Foo<T> {
fun bar(vararg a: T)
}
class NormalDouble: Foo<java.lang.Double> {
override fun bar(vararg a: java.lang.Double) {
throw UnsupportedOperationException()
}
}
class BadDouble: Foo<kotlin.Double> {
override fun bar(vararg a: kotlin.Double) {
throw UnsupportedOperationException()
}
}
Для vararg a: T генерируется Object[] a, и для vararg a: Int это int[] a , что не является переопределением.
Есть один вариант пофиксить это: для bar компилятор будет генерировать Integer[], вместо int[].
Но другая проблема возникает здесь:
val bi = BadInt()
val arr = intArrayOf(1, 2)
bi.bar(*arr) // Здесь проблема
Вообще можно сделать отдельный оператор, который будет боксить каждый элемент массива, но это будет очень медленно. Единственный вариант для решения этой проблемы без изменения дизайна языка — ждать релиза проекта Valhalla.
Так и Котлин не может kotlin.Double сконвертить в int для дженериков, поскольку сами дженерики и там и там практически идентичны. Да, он может это не пишет явно в сообщение об ошибке, но тут догадаться и так можно почему это происходит.
Чтобы исправить данный пример можно отметить в самом дженерике тип как kotlin.Double? тогда все будет успешно. Или просто убрать дженерики вообще и написать:
fun bar(vararg a: Double) {
throw UnsupportedOperationException("not implemented")
}
тогда он сгенерит:
public final void bar(double... a) {
throw (Throwable)(new UnsupportedOperationException("not implemented"));
}
что вероятно и хотелось изначально.
kotlin.Double тоже не работает.
kotlin.Double? — выделил важное жирным
Но идея выкинуть дженерики из языка хорошая, мне нравится.
Не знаю откуда вы это взяли вообще.
А вообще таск открыт.
Читайте внимательней комментарий к таску, там как раз все развернуто описано.
Meanwhile, the questions is what's better:
- (the present behavior) we refuse to compile something that intuitively should be compilable;
- we compile it, but introduce a performance penalty that the user has little chance to be aware of at the time of writing the code.
В текущей ситуации, вам кажется что код выше, т.е.:
class BadDouble: Foo<kotlin.Double> {
override fun bar(vararg a: kotlin.Double) {
throw UnsupportedOperationException()
}
}
Должен скомпилироваться в:
public final class BadDouble implements Foo<double>
{
public void bar(double... a)
{
throw ((Throwable)new UnsupportedOperationException("not implemented"));
}
}
Но такая конструкция в текущей версии Java просто не поддерживается, как это уже писал выше.
Какие есть варианты решения проблемы?
1. (как сейчас) выдавать ошибку компиляции
2. не явно преобразовывать Integer[] и вставлять хаки при конвертации между int[] и bar(vararg a: kotlin.Double).
А ещё в IDEA есть специальный инструмент, показывающий во что на уровне байткода превращается код нп Kotlin. Чтобы не надо было гадать.
И да. Смотреть туда довольно полезно, если вы действительно заботитесь о производительности. Иногда довольно занятные перлы обнаруживаются. Которые, впрочем исправляются с минорными релизами, если об этом сообщать разработчикам.
Kotlin и autoboxing