Kotlin (не) против Java: особенности компиляции в байткод

2fab995e8063c0fc0c1c25c0f5a4700d.png

В 1995 году Sun Microsystems представили Java — объектно-ориентированный язык программирования, основное кредо которого можно сформулировать так: «Написано один раз, работает всегда». В 2011 году как улучшенную альтернативу Java компания JetBrains представила Kotlin — язык с той же философией, но иной реализацией. С тех пор в сообществе программистов между адептами Java и Kotlin ведется непримиримая вражда…

Всем привет! Меня зовут Артем Панасюк, я ведущий backend-разработчик на Java/Kotlin в «Леруа Мерлен». В этом тексте я постараюсь залезть к этим языкам «под капот» и посмотреть, правда ли они такие разные — и в чем преимущества каждого из них.

Присоединяйтесь, будет интересно!

Java vs Kotlin:, а есть ли вражда?

Постараемся избежать холиваров и остановимся на фактах. С одной стороны, Java гораздо популярнее: им пользуются 30% всех разработчиков против 9% у Kotlin (или даже 40% против 8% — по другим данным). С другой стороны, Kotlin своим любимым языком называют 63% разработчиков, тогда как Java — всего 44%, а у 56% она вообще вызывает страх. Даже если сделать поправку на то, каким именно образом собиралась статистика и кто попал в базу респондентов, тренд налицо.

При этом сущностно языки похожи. Читабельный код, написанный программистом, компилируется в байткод — и скармливается виртуальной машине, в обоих случаях — Java Virtual Machine. Изначально маркетинг вообще строился на том, что Kotlin — это как Java, только лучше. Сегодня Kotlin «перерос» эти рамки, и сейчас его экосистема позволяет проводить компиляцию не только в байткод Java, но и в нативный машинный код других платформ (iOS, macOS, Windows, Linux, WebAssembly), а также создавать мобильные и десктопные приложения без необходимости использовать JVM в целом.

Поскольку виртуальные машины существуют для самых разных операционных систем, разработчикам удобно использовать эти языки для создания мультиплатформенных приложений. Java в этом смысле стала общим местом при написании софта для Android и Windows/Linux/macOS, Kotlin — для программирования под Android/iOS. Несложно заметить, что Android объединяет оба языка. Долгое время Java использовалась в качестве основного языка, на котором пишутся apk-шки. Но в 2017 году Google передала статус официального языка Android Kotlin«у, аргументировав выбор его большей лаконичностью и производительностью.

Однако, несмотря на поддержку Google«а, лидирующие позиции Kotlin занять не смог. И сегодня оба языка существуют и используются параллельно — во многом благодаря легаси Java и удобству Kotlin. А теперь давайте разберемся, в чем и насколько сильно языки действительно превосходят друг друга.

Что мы будем делать?

В вопросе сравнения на помощь нам приходит тот факт, что и Kotlin, и Java «перевариваются» с помощью Java Virtual Machine (JVM). Код на обоих языках компилируется в *.class и финально в *.jar —, а значит, с помощью реверсивного восстановления байткод можно превратить в понятный человеку синтаксис на любом из языков. «Фарш невозможно провернуть назад, и мясо из котлет не восстановишь» — в нашем случае миф, ложь и неправда.

bb038ead84bccb13ef8fdce3280a9ca6.png

Так и сделаем: воспользуемся опенсорсным декомпилятором Fernflower от JetBrains и путем несложных манипуляций переведем отрывки кода на Kotlin в их аналог на Java. А теперь посмотрим на основы синтаксиса, а также на киллер-фичи Kotlin — лямбда-функции и (кратко) корутины.

Основные конструкции

Здесь стоит сделать небольшую ремарку: названия классов и переменных в Java-коде были даны декомпилятором автоматически, поэтому могут выглядеть немного странно.

Здесь видно, как легко в Kotlin «из коробки» реализован паттерн ДТО — в Java для тех же целей необходимо подключать внешнюю библиотеку или писать лишний код

Здесь видно, как легко в Kotlin «из коробки» реализован паттерн ДТО — в Java для тех же целей необходимо подключать внешнюю библиотеку или писать лишний код

Пример «плохого», или неидеоматичного, кода на Kotlin. Похоже на код разработчика, который пересел на Kotlin после Java

Пример «плохого», или неидеоматичного, кода на Kotlin. Похоже на код разработчика, который пересел на Kotlin после Java

Изящное решение проблемы: когда нужный параметр не передается в метод, то в ход идет дефолтное значение

Изящное решение проблемы: когда нужный параметр не передается в метод, то в ход идет дефолтное значение

Паттерн Singleton в случае Kotlin спрятан в одном служебном слове «Object». На Java придется вспоминать, как он пишется

Паттерн Singleton в случае Kotlin спрятан в одном служебном слове «Object». На Java придется вспоминать, как он пишется

85717c10364ba60c9a8bfe9edc0f050e.png

Основной вывод, который сразу бросается в глаза, — лаконичность кода на Kotlin. Собственно, его создатели из JetBrains и адепты из Google использовали этот факт как один из весомых аргументов в пользу преимущества языка. Таким образом, файлы с кодом весят меньше, программистам проще писать, а главное — уменьшается пространство для возможных багов. Понятно, что современные решения помогают программистам находить баги в автоматическом режиме и не дадут скомпилировать забагованный код, но факт остается фактом: чем меньше кода, тем меньше ошибок.

С другой стороны, Kotlin… проигрывает в производительности, вопреки заверениям Google. Да, речь идет о разнице в наносекунды или доли наносекунд — и в большинстве случаев мощности современного железа позволяют разработчикам закрыть на это глаза. Но иногда даже такое отставание может быть существенно.

Наконец, бойлерплейт-код и синтаксический сахар. Первое — это масса шаблонного кода, которая не несет никакой смысловой нагрузки и присутствует в программе исключительно из-за особенностей языка программирования. Одна и та же функциональная единица в Kotlin может занимать две строки, а на Java — десять. И разница в 8 строк — это практически белый шум, ведь по итогу компьютер увидит одно и то же. 

Синтаксический сахар — это, наоборот, использование синтаксических возможностей языка, которые не влияют на содержание, но делают код понятнее и нагляднее. В Kotlin длинные конструкции можно заменять более короткими — это одна из фишек языка. Однако в некоторых случаях такое стремление к минимизации и оптимизации может сыграть злую шутку. И для того, чтобы использовать этот инструмент эффективно, нужно знать не только синтаксис языка, но и его логику, иметь глубинное понимание основ работы.

Лямбда-функции

На момент выхода Kotlin лямбда-функции в Java отсутствовали — их добавили гораздо позднее, из-за чего Kotlin в том числе начал завоевывать популярность. Из-за этого реализация функций в Kotlin независима от Java. И вполне успешна: даже сегодня, после того как лямбда-функции в Java все-таки появились, их реализация в Kotlin имеет меньше ограничений.

Пример функционального программирования и простейшего лямбда-выражения. В Kotlin реализовано без каких-либо Java-библиотек. В Java тот же функционал станет доступен только после восьмой версии

Пример функционального программирования и простейшего лямбда-выражения. В Kotlin реализовано без каких-либо Java-библиотек. В Java тот же функционал станет доступен только после восьмой версии

Функциональный интерфейс из состава стандартной библиотеки Kotlin и его аналог на Java. Сама лямбда в Java-версии видна в методе invoke

Функциональный интерфейс из состава стандартной библиотеки Kotlin и его аналог на Java. Сама лямбда в Java-версии видна в методе invoke

Одно из ключевых отличий лямбда-функций в Kotlin и Java — реализация специализированных классов, которая гарантирует иммутабельность, или постоянство, значений. Подход к типам в Kotlin изначально отличается от Java: любая переменная может быть объектом. Это позволяет, например, захватывать переменные внутрь лямбды — в Java это запрещено.

Поэтому из общих выводов можно выделить два основных. Первый: в целом реализация лямбда-функций на Java и Kotlin примерно идентична. Не с точки зрения синтаксиса, а с точки зрения удобства и производительности. Да, Kotlin в этом смысле чуть более гибок, но и Java к последним версиям стала куда продвинутее. Однако второй вывод: в Java, в отличие от Kotlin, невозможно работать с иммутабельностью, и это качественно другой подход, который позволяет Kotlin снизить число ошибок.

Корутины

Не станем глубоко вдаваться в концепцию корутин — это материал для отдельного текста.

Для решения задач параллельного выполнения есть несколько подходов.

  • Threads — классический подход, который реализован в том числе в Java; ограничен по ресурсам, например, памяти (на один тред выделяется ~2 Mб на уровне ОС).

  • Callback — также реализован в Java; в случае ошибок приводит к «аду обратных вызовов» (callback hell), в котором катастрофически сложно разобраться.

  • Future/Promise/RX — особый раздел, реактивное программирование, требует больших человеческих ресурсов на погружение.

  • Coroutines — просты по кодовой базе и синтаксису, легковесны и не имеют таких жестких ограничений, как Threads.

Сама концепция корутин не нова — название появилось еще в 1958 году. Поддержку решения предоставляют многие современные языки программирования: C#, Go, Python, Ruby, Kotlin… Но не Java.

И именно корутины во многом обусловили выбор Kotlin в качестве официального языка разработки на Android — вместо Java — и популярность языка в целом. Не так давно схожий функционал был реализован в Java посредством Project Loom, однако момент уже упущен.

Итоги

Мы убедились, что сравнение этих языков программирования — тема скорее кликбейтная и холиварная, чем по-настоящему содержательная: сегодня существенных различий между Java и Kotlin нет. В последних обновлениях функционал языков во многом стал идентичен, а особенности синтаксиса и разницу в производительности сложно назвать явным преимуществом любого из них. Да, Java воспринимается чуть более устаревшей, а Kotlin — модным-молодежным, но по существу у обоих есть свои плюсы и минусы, фанаты и хейтеры.

В «Леруа Мерлен» значительная часть ПО пишется на Kotlin. Во многом это продиктовано историческим контекстом: активный период цифровизации и развития собственных ИТ-сервисов пришелся на «золотой» период развития языка, когда тот отвоевывал у Java место на рынке, но и сегодня его использование позволяет нам быть актуальными в своем стеке.

На Kotlin, например, разрабатывалось мобильное приложение под iOS и Android. И я по собственному опыту (Kotlin стал моим вторым языком после Java) и по опыту коллег вижу, что неполное понимание работы языка мешает использовать его на сто процентов.

Те, кто учит Kotlin как второй язык, часто начинают писать на нем, пользуясь той же логикой, что пользовались с Java. Это не дает языку полностью раскрыть свой потенциал — код работает (и это плюс Kotlin — его гибкость), но не так эффективно. И чтобы добиться от своей работы максимума продуктивности, нужно понять, что находится у Kotlin «под капотом». Да, при желании можно забить шуруп молотком — он сработает так, как в этот момент нужно человеку. И тем не менее, если использовать его по назначению, КПД у процесса вырастет многократно.

Так что помимо сравнения языков моей задачей было разобраться в том, как на самом деле работает Kotlin, как его логику воспринимает компьютер. Здесь его родство с Java сыграло нам на руку — и я надеюсь, что через сравнение мы смогли лучше понять, почему на самом деле разработчики «любят» Kotlin. А что по этому поводу думаете вы? Делитесь своим мнением в комментариях.

© Habrahabr.ru