Pull to refresh

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-компиляторе (например, на длину байткода метода) и лишним нопом отключить какую-нибудь оптимизацию.

Мне кажется достаточно трудно делать какие-то выводы об оптимизациях на уровне JVM в данном случае без отрыва от тестов. Вообще зачем действительно там nop мне трудно сказать, могу лишь предположить, что возможно это как-то связано с дебагом.
Раз проверка не атомарна, значит ли это что null-safety в котлине не гарантируется при многопоточности?
Естественно нет, он не будет за вас создавать синхронизацию и прочее. Для большинства случаев это просто не нужно, например если null переменная объявляется внутри метода.

Никто не мешает перегрузить значение из кучи в локальную переменную или на стек однократно. А локальные переменные и стек в Java не могут измениться из других потоков — это закон. Так что, думаю, здесь всё хорошо.

На всякий случай: вот тут есть небольшое выступление Жемерова о том как и во что компилируется Котлин.
С массивами не всё так однозначно, на самом деле. Есть большая проблема с vararg'ами.
UFO just landed and posted this here
В первом случае все нормально, а во втором получается compilation error. И проблема в дизайне языка.

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.
Причем тут дизайн языка, да и vararg вообще, проблема как раз в самой платформе: в Java в принципе нельзя сделать метод с примитивом в качестве generic параметра, что вы и пытаетесь осуществить. Попробуйте переписать пример выше на обычной джаве и скормить там в качестве параметра типа обычный int — на текущей версии Java так сделать нельзя, почему это происходит кидал ссылку в статье про боксинг.

Так и Котлин не может 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 тоже не работает.

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).
UFO just landed and posted this here

А ещё в IDEA есть специальный инструмент, показывающий во что на уровне байткода превращается код нп Kotlin. Чтобы не надо было гадать.


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

Sign up to leave a comment.

Articles