У меня в статье выдержка только сообщений котлина, взятая из его исходников, так что ничего универсального в ней и быть не может.
Иметь авто-дополнение для известных типов, конечно, хорошо, но только ради этого тащить кучу кода в любой проект, по моему, неадекватно и для эксклюзивов это никак не поможет.
Типовой кейс: вылезает ошибка, которую надо исключить, копирую уникальную часть текста (с этим самые большие сложности т.к. в IntelliJ окно с сообщениями об ошибках — это боль), ищу его в таблице, копирую тип и втыкаю в исходник.
В кнопках, помимо войны с текстом сообщения в IntelliJ, это все очень быстро: Alt-Tab, F4, F7, Ctrl-V, Enter пара шевелений стрелками, Ctrl-C и уже Alt-Tab обратно с типом.
А насчет создания списка для использования автодополнения (для чего ты, наверное, и сделал класс) я думал.
С этого начал, но оказалось, что пользоваться этим ничуть не удобнее чем отдельной таблицей т.к. искать сообщение все равно можно только по тексту.
Да и как-то жаба душит тащить кучу абсолютно ненужного кода в либу. По привычке.
Все зависит от того чем заниматься.
Если писать приложение, то воевать с ошибками приходится чрезвычайно редко.
Если же писать либу, то часто.
У меня безусловный победитель по количеству использований — это UNCHECKED_CAST, 90% применений которого приходится на бездарные шаблоны (женерики), которые неспособны приводить типы.
Но бывают и эксклюзивы, типа:
class PLog(private val allowlog : Boolean = true) {
...
@Suppress("NON_PUBLIC_CALL_FROM_PUBLIC_INLINE")
inline fun log( cb:()->String ) =
if (allowlog) note(indent.indent() + cb() + "\n")
...
Открывать поле я не хочу, а избавиться от ошибки синтаксическими способами я не смог.
В любом случае создается класс.
Какая разница как это делается, компилятором при сборке программы или генерацией его кода на лету через миллион библиотечных прослоек?
Лично мне, после беглого разглядывания java\lang\invoke*.java, гораздо более симпатичен подход, который используется именно в Kotlin.
ПС: Я не думаю что в Kotlin изменят способ обработки кода т.к. одной из задач этого компилятора собираться под мобильные платформы а, насколько я знаю, даже на Android, существенные проблемы с генерацией кода на лету.
Но я не специалист, поэтому в обсуждение ввязываться не буду :)
Я, в свою очередь, не знаком с деталями Java, но то что я вижу сейчас в сгенерированном из Java коде производит абсолютно такие же действия, как и компилятор Kоtlin: создается новый класс для каждого места использования лямбды и ему в конструкторе передаются захваченные переменные.
Насколько я понял "LambdaMetafactory" — это возможность для пользователя. Компилятор Java ее не использует.
Сейчас реализация Kotlin описана более подробно в текущем варианте статьи выше.
Я имел в виду то, что в Java с какой-то версии (1.2 или 1.4 — не разбирался точно) появилась возможность в одном файле класса описывать сколько угодно классов верхнего уровня.
Но в первоначальной статье у меня была ошибка, поэтому в текущем варианте это уже не важно.
Ткни плз пальцем в конкретный пример ее использования.
Я почитал документацию, но мне показалось ее использование излишне усложненным, а простые примеры не папались.
Дык это и правильно — описание и должно соответствовать документации, иначе это дезинформация :)
Правда эта документация…
В документации Java есть многое, но проблема в том, что мест где все это сведено воедино с примерами применения и ссылками на аналоги, как правило нет и приходится рыть кучи альтернативных источников.
Из-за этого, собственно, и свел все в один текст.
ПС: Текст статьи поправил, спасибо за уточнение.
Отдельное спасибо за наводку о возможности реализовывать интерфейсы делегатами. Не знал об этом :)
Согласен, можно реализовать интерфейс без маркера Serializable.
Переформулирую так:
Поддерживается сохранение и восстановление любых объектов, которые имеет интерфейс-маркер Serializable. В частности, будут автоматически сохраняться все стандартные JDK коллекции, основанные на List, Set и Map т.к. все их реализации этот маркер имеют.
С точки зрения меня (если поставить себя на место разработчиков) довольно сложно называть это проблемой, и тем более проблемой именно Kotlin.
Разработчики сохраняют классы в отдельные файлы а, при использовании inline, пользуются возможностью появившейся в Java, которая позволяет разместить в одном файле несколько классов.
То, что в первом случае файлы обладают гигантским оверхедом к коду, в сравнении с размещением их в уже существующем файле, ставить в вину нужно скорее тем, кто придумал формат class-файлов, а не разработчикам Kotlin.
На мой взгляд описанное в статье — это скорее "фича", которую имеет смысл знать и использовать, а не какая-то проблема.
Проблемой можно назвать отсутствие какой-нибудь оптимизации, но, я уверен, это известно и без меня и, если язык будет развиваться, она появится.
ПС: Повторно прошу обосновать утверждение о вреде, которому я "учу".
Я так и не понял практического смысла. Да у вас jar будет на пару кб больше. И?
Практический смысл именно тот, чтобы не было лишнего, абсолютно бессмысленного кода.
К примеру, каждый раз, когда в программе используется конструкция:
fun Action( cb:()->Unit ) {}
fun statMethod() {}
fun Test() {
Action( ::statMethod ) // эта конструкция
}
Будет сгенерирован очередной уникальный класс, который от прошлого использования такой конструкции будет иметь всего одно отличие: номер в имени.
Если Вас абсолютно не волнует какой код будет сгенерирован компилятором и как он будет работать, лишь бы программа выполнялась, то эту статью можно с чистой совестью забыть.
Меня лично такие вещи интересуют и "плохо пахнущим" кодом я как раз считаю такой, где разработчику абсолютно дофонаря какую именно конструкцию использовать.
Но это мои тараканы, я не навязываю :)
Да у вас jar будет на пару кб больше. И?
Насчет пары кб — это откуда взялось?
В тексте выше же есть цифры. КАЖДОЕ использование такой конструкции добавит около полутора Кб.
Если их в коде используется сто, то будет + 1.5Мб...
Вообще, эта статья родилась как результат исследования, которым я занялся, когда с удивлением обнаружил что тест для моего DSL, не имея практически никакого кода с текстом на пру экранов, занимает в 4 раза больше, чем лежащая рядом софтина в размером исходников в пару сотен Кб.
Заинтересовался, выяснил отчего так происходит.
Решил поделиться.
Очень просто, если у вас размер метода такими инлайнами вырастет
Либо я чего-то в принципе не понимаю, либо Вы.
О размере какого метода идет речь?
Код, который генерируется в месте объявления автоматического класса, абсолютно никак не зависит от того в каком именно файле этот класс физически расположен.
Наличие inline влияет только на место расположения сгенерированного класса и не более того.
Наверное я криво это описал в тексте?
Стоит как-то исправить или примера выше будет достаточно?
На счет аннотаций Kotlin спасибо, почерпну.
Вообще в Kotlin крайне много недокументированного, что часто раздражает.
Например пришлось из его исходников вырезать списки ошибок и предупреждений, чтобы знать какой текст писать в @Suppress("?")
Абсолютно нельзя доверять.
Ровно как и любому другому бенчмарку в любой области, буть то Antutu для телефонов или мой тест для библиотеки сериализации.
Любой тест — это замер очень конкретных кода и данных. Единственное что можно сделать при тестировании универсальным — это нивелировать влияние платформы на которой запускается тест на его результаты. Я постарался это сделать. В остальном же, результаты будут очень сильно зависеть от той модели данных, которой оперирует тест.
Возьмем, к примеру, библиотеку "minimal-json". Я не утверждаю что тесты, опубликованные на ее странице являются полным враньем только потому, что на моих данных она демонстрирует в разы худшие результаты. Различные данные накладывают свой отпечаток на результаты.
Если посмотреть тесты, которые используются для этой библиотеки, то видно, что они основаны на очень маленьких наборах данных (примеры для тестов по 20-50Кб), тогда как в моем тесте за один раз обрабатывались объемы данных на два порядка больше.
Во-первых, мне лично неинтересны результаты тестирования для маленьких наборов данных и, во-вторых, я считаю что работа с большими объемами демонстрирует преимущества и недостатки библиотек гораздо более ярко. Поэтому я считаю что мой тест для этой конкретной библиотеки демонстрирует более адекватные результаты чем те, которые приведены на ее сайте.
Итого, возвращаясь к вопросу о доверии.
Если данные, которыми Вы планируете оперировать более похожи на мои по объему и\или типу, то доверять соотношению производительности сравниваемых средств вполне можно. Если бы я так не считал, то не опубликовал бы статью вообще :)
В любом случае, практически в любом тесте можно доверять соотношения. Если какой-то код в более чем одном тесте оказывается медленнее другого, то можно сделать вполне адекватный вывод о том, что он будет всегда работать медленнее. Другое дело, что на разных данных величина различия может быть разной, но само отношение все равно сохранится.
Простейшая библиотека для парсинга xml (!), которую я использую повседневно, работает на порядок (!!) быстрее лидеров этого теста. Правда реализована она на С++.
Я специально убил довольно много времени на утилиту тестирования и на эту статью для того, чтобы не только поделиться своими результатами, но и обеспечить возможность всем провести собственные тесты именно для JVM.
Если возникают сомнения в моих результатах, то можно вынуть исходники утилиты тестирования, собрать ее, погонять в разных условиях и сделать собственные выводы.
Переделать модель используемых данных в ней будет довольно сложно, но поиграть с объемами можно легко.
Большинство библиотек позиционируют себя как "на порядок быстрее чем ХХХ".
К примеру библиотека "minimal-json", использованная в тесте, попала в него именно благодаря само-позиционированию, как одна из самых скоростных. К сожалению, мои измерения это не подтвердили.
Библиотека "RuedigerMoeller/fast-serialization", конечно, в 10 раз быстрее физически быть не может, но, возможно она действительно быстрая.
Можно ее добавить в тест, если это кому-нибудь интересно.
Методы putFields \ readFields используются немного для другого.
Они предназначены для загрузки полей, сохраненных ранее с другими именами или типами и, соответственно, для сохранения текущих полей в формате, который отличается от существующего в объекте.
Добиться такого поведения простым сохранением объекта невозможно. Вернее, чтобы это обеспечить придется все сохранение\восстановление реализовывать самостоятельно.
Вообще, после тестирования я лично от стандартной сериализации решил отказаться совсем.
Чтобы она работала быстро ее приходится реализовывать очень многословно и решать много проблем и, при этом, из-за особенностей формата, сохраненный результат оказывается сильно зависим от того формата объекта, который использовался при сохранении.
В результате, получаем либо медленно но универсально, либо быстро но кропотливо, т.е. штатная сериализация имеет неоспоримое преимущество в довольно узком диапазоне задач.
У меня в статье выдержка только сообщений котлина, взятая из его исходников, так что ничего универсального в ней и быть не может.
Иметь авто-дополнение для известных типов, конечно, хорошо, но только ради этого тащить кучу кода в любой проект, по моему, неадекватно и для эксклюзивов это никак не поможет.
Типовой кейс: вылезает ошибка, которую надо исключить, копирую уникальную часть текста (с этим самые большие сложности т.к. в IntelliJ окно с сообщениями об ошибках — это боль), ищу его в таблице, копирую тип и втыкаю в исходник.
В кнопках, помимо войны с текстом сообщения в IntelliJ, это все очень быстро: Alt-Tab, F4, F7, Ctrl-V, Enter пара шевелений стрелками, Ctrl-C и уже Alt-Tab обратно с типом.
Вообще вопроса не понял.
А насчет создания списка для использования автодополнения (для чего ты, наверное, и сделал класс) я думал.
С этого начал, но оказалось, что пользоваться этим ничуть не удобнее чем отдельной таблицей т.к. искать сообщение все равно можно только по тексту.
Да и как-то жаба душит тащить кучу абсолютно ненужного кода в либу. По привычке.
Все зависит от того чем заниматься.
Если писать приложение, то воевать с ошибками приходится чрезвычайно редко.
Если же писать либу, то часто.
У меня безусловный победитель по количеству использований — это UNCHECKED_CAST, 90% применений которого приходится на бездарные шаблоны (женерики), которые неспособны приводить типы.
Но бывают и эксклюзивы, типа:
Открывать поле я не хочу, а избавиться от ошибки синтаксическими способами я не смог.
При чем тут практика вообще?
Это таблица всех существующих сообщений.
Нигде не написано что их все нужно или можно маскировать.
В любом случае создается класс.
Какая разница как это делается, компилятором при сборке программы или генерацией его кода на лету через миллион библиотечных прослоек?
Лично мне, после беглого разглядывания java\lang\invoke*.java, гораздо более симпатичен подход, который используется именно в Kotlin.
ПС: Я не думаю что в Kotlin изменят способ обработки кода т.к. одной из задач этого компилятора собираться под мобильные платформы а, насколько я знаю, даже на Android, существенные проблемы с генерацией кода на лету.
Но я не специалист, поэтому в обсуждение ввязываться не буду :)
Я, в свою очередь, не знаком с деталями Java, но то что я вижу сейчас в сгенерированном из Java коде производит абсолютно такие же действия, как и компилятор Kоtlin: создается новый класс для каждого места использования лямбды и ему в конструкторе передаются захваченные переменные.
Насколько я понял "LambdaMetafactory" — это возможность для пользователя. Компилятор Java ее не использует.
Сейчас реализация Kotlin описана более подробно в текущем варианте статьи выше.
Я имел в виду то, что в Java с какой-то версии (1.2 или 1.4 — не разбирался точно) появилась возможность в одном файле класса описывать сколько угодно классов верхнего уровня.
Но в первоначальной статье у меня была ошибка, поэтому в текущем варианте это уже не важно.
Ткни плз пальцем в конкретный пример ее использования.
Я почитал документацию, но мне показалось ее использование излишне усложненным, а простые примеры не папались.
Дык это и правильно — описание и должно соответствовать документации, иначе это дезинформация :)
Правда эта документация…
В документации Java есть многое, но проблема в том, что мест где все это сведено воедино с примерами применения и ссылками на аналоги, как правило нет и приходится рыть кучи альтернативных источников.
Из-за этого, собственно, и свел все в один текст.
ПС: Текст статьи поправил, спасибо за уточнение.
Отдельное спасибо за наводку о возможности реализовывать интерфейсы делегатами. Не знал об этом :)
Согласен, можно реализовать интерфейс без маркера Serializable.
Переформулирую так:
Поддерживается сохранение и восстановление любых объектов, которые имеет интерфейс-маркер Serializable. В частности, будут автоматически сохраняться все стандартные JDK коллекции, основанные на List, Set и Map т.к. все их реализации этот маркер имеют.
подходит?
С точки зрения меня (если поставить себя на место разработчиков) довольно сложно называть это проблемой, и тем более проблемой именно
Kotlin
.Разработчики сохраняют классы в отдельные файлы а, при использовании inline, пользуются возможностью появившейся в Java, которая позволяет разместить в одном файле несколько классов.
То, что в первом случае файлы обладают гигантским оверхедом к коду, в сравнении с размещением их в уже существующем файле, ставить в вину нужно скорее тем, кто придумал формат
class
-файлов, а не разработчикамKotlin
.На мой взгляд описанное в статье — это скорее "фича", которую имеет смысл знать и использовать, а не какая-то проблема.
Проблемой можно назвать отсутствие какой-нибудь оптимизации, но, я уверен, это известно и без меня и, если язык будет развиваться, она появится.
ПС: Повторно прошу обосновать утверждение о вреде, которому я "учу".
Практический смысл именно тот, чтобы не было лишнего, абсолютно бессмысленного кода.
К примеру, каждый раз, когда в программе используется конструкция:
Будет сгенерирован очередной уникальный класс, который от прошлого использования такой конструкции будет иметь всего одно отличие: номер в имени.
Если Вас абсолютно не волнует какой код будет сгенерирован компилятором и как он будет работать, лишь бы программа выполнялась, то эту статью можно с чистой совестью забыть.
Меня лично такие вещи интересуют и "плохо пахнущим" кодом я как раз считаю такой, где разработчику абсолютно дофонаря какую именно конструкцию использовать.
Но это мои тараканы, я не навязываю :)
Насчет пары кб — это откуда взялось?
В тексте выше же есть цифры.
КАЖДОЕ использование такой конструкции добавит около полутора Кб.
Если их в коде используется сто, то будет + 1.5Мб...
Вообще, эта статья родилась как результат исследования, которым я занялся, когда с удивлением обнаружил что тест для моего DSL, не имея практически никакого кода с текстом на пру экранов, занимает в 4 раза больше, чем лежащая рядом софтина в размером исходников в пару сотен Кб.
Заинтересовался, выяснил отчего так происходит.
Решил поделиться.
Либо я чего-то в принципе не понимаю, либо Вы.
О размере какого метода идет речь?
Код, который генерируется в месте объявления автоматического класса, абсолютно никак не зависит от того в каком именно файле этот класс физически расположен.
Наличие inline влияет только на место расположения сгенерированного класса и не более того.
Это значит, что поля с данными такого типа Serializable сохранит и восстановит автоматически.
Иллюстрация:
Вывод:
Наверное я криво это описал в тексте?
Стоит как-то исправить или примера выше будет достаточно?
На счет аннотаций Kotlin спасибо, почерпну.
Вообще в Kotlin крайне много недокументированного, что часто раздражает.
Например пришлось из его исходников вырезать списки ошибок и предупреждений, чтобы знать какой текст писать в
@Suppress("?")
Обоснование моего вывода описано выше.
Обоснование того что это вредно привести можно?
Так же было бы очень интересно узнать о том, где inline нельзя использовать или хотя бы те, где его наличие и отсутствие хоть на что-то влияло бы.
Абсолютно нельзя доверять.
Ровно как и любому другому бенчмарку в любой области, буть то Antutu для телефонов или мой тест для библиотеки сериализации.
Любой тест — это замер очень конкретных кода и данных. Единственное что можно сделать при тестировании универсальным — это нивелировать влияние платформы на которой запускается тест на его результаты. Я постарался это сделать. В остальном же, результаты будут очень сильно зависеть от той модели данных, которой оперирует тест.
Возьмем, к примеру, библиотеку "minimal-json". Я не утверждаю что тесты, опубликованные на ее странице являются полным враньем только потому, что на моих данных она демонстрирует в разы худшие результаты. Различные данные накладывают свой отпечаток на результаты.
Если посмотреть тесты, которые используются для этой библиотеки, то видно, что они основаны на очень маленьких наборах данных (примеры для тестов по 20-50Кб), тогда как в моем тесте за один раз обрабатывались объемы данных на два порядка больше.
Во-первых, мне лично неинтересны результаты тестирования для маленьких наборов данных и, во-вторых, я считаю что работа с большими объемами демонстрирует преимущества и недостатки библиотек гораздо более ярко. Поэтому я считаю что мой тест для этой конкретной библиотеки демонстрирует более адекватные результаты чем те, которые приведены на ее сайте.
Итого, возвращаясь к вопросу о доверии.
Если данные, которыми Вы планируете оперировать более похожи на мои по объему и\или типу, то доверять соотношению производительности сравниваемых средств вполне можно. Если бы я так не считал, то не опубликовал бы статью вообще :)
В любом случае, практически в любом тесте можно доверять соотношения. Если какой-то код в более чем одном тесте оказывается медленнее другого, то можно сделать вполне адекватный вывод о том, что он будет всегда работать медленнее. Другое дело, что на разных данных величина различия может быть разной, но само отношение все равно сохранится.
Я специально убил довольно много времени на утилиту тестирования и на эту статью для того, чтобы не только поделиться своими результатами, но и обеспечить возможность всем провести собственные тесты именно для JVM.
Если возникают сомнения в моих результатах, то можно вынуть исходники утилиты тестирования, собрать ее, погонять в разных условиях и сделать собственные выводы.
Переделать модель используемых данных в ней будет довольно сложно, но поиграть с объемами можно легко.
Большинство библиотек позиционируют себя как "на порядок быстрее чем ХХХ".
К примеру библиотека "minimal-json", использованная в тесте, попала в него именно благодаря само-позиционированию, как одна из самых скоростных. К сожалению, мои измерения это не подтвердили.
Библиотека "RuedigerMoeller/fast-serialization", конечно, в 10 раз быстрее физически быть не может, но, возможно она действительно быстрая.
Можно ее добавить в тест, если это кому-нибудь интересно.
Методы putFields \ readFields используются немного для другого.
Они предназначены для загрузки полей, сохраненных ранее с другими именами или типами и, соответственно, для сохранения текущих полей в формате, который отличается от существующего в объекте.
Добиться такого поведения простым сохранением объекта невозможно. Вернее, чтобы это обеспечить придется все сохранение\восстановление реализовывать самостоятельно.
Вообще, после тестирования я лично от стандартной сериализации решил отказаться совсем.
Чтобы она работала быстро ее приходится реализовывать очень многословно и решать много проблем и, при этом, из-за особенностей формата, сохраненный результат оказывается сильно зависим от того формата объекта, который использовался при сохранении.
В результате, получаем либо медленно но универсально, либо быстро но кропотливо, т.е. штатная сериализация имеет неоспоримое преимущество в довольно узком диапазоне задач.