В 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 — а значит, с помощью реверсивного восстановления байткод можно превратить в понятный человеку синтаксис на любом из языков. «Фарш невозможно провернуть назад, и мясо из котлет не восстановишь» — в нашем случае миф, ложь и неправда.
Так и сделаем: воспользуемся опенсорсным декомпилятором Fernflower от JetBrains и путем несложных манипуляций переведем отрывки кода на Kotlin в их аналог на Java. А теперь посмотрим на основы синтаксиса, а также на киллер-фичи Kotlin — лямбда-функции и (кратко) корутины.
Основные конструкции
Здесь стоит сделать небольшую ремарку: названия классов и переменных в Java-коде были даны декомпилятором автоматически, поэтому могут выглядеть немного странно.
Основной вывод, который сразу бросается в глаза, — лаконичность кода на Kotlin. Собственно, его создатели из JetBrains и адепты из Google использовали этот факт как один из весомых аргументов в пользу преимущества языка. Таким образом, файлы с кодом весят меньше, программистам проще писать, а главное — уменьшается пространство для возможных багов. Понятно, что современные решения помогают программистам находить баги в автоматическом режиме и не дадут скомпилировать забагованный код, но факт остается фактом: чем меньше кода, тем меньше ошибок.
С другой стороны, Kotlin… проигрывает в производительности, вопреки заверениям Google. Да, речь идет о разнице в наносекунды или доли наносекунд — и в большинстве случаев мощности современного железа позволяют разработчикам закрыть на это глаза. Но иногда даже такое отставание может быть существенно.
Наконец, бойлерплейт-код и синтаксический сахар. Первое — это масса шаблонного кода, которая не несет никакой смысловой нагрузки и присутствует в программе исключительно из-за особенностей языка программирования. Одна и та же функциональная единица в Kotlin может занимать две строки, а на Java — десять. И разница в 8 строк — это практически белый шум, ведь по итогу компьютер увидит одно и то же.
Синтаксический сахар — это, наоборот, использование синтаксических возможностей языка, которые не влияют на содержание, но делают код понятнее и нагляднее. В Kotlin длинные конструкции можно заменять более короткими — это одна из фишек языка. Однако в некоторых случаях такое стремление к минимизации и оптимизации может сыграть злую шутку. И для того, чтобы использовать этот инструмент эффективно, нужно знать не только синтаксис языка, но и его логику, иметь глубинное понимание основ работы.
Лямбда-функции
На момент выхода Kotlin лямбда-функции в Java отсутствовали — их добавили гораздо позднее, из-за чего Kotlin в том числе начал завоевывать популярность. Из-за этого реализация функций в Kotlin независима от Java. И вполне успешна: даже сегодня, после того как лямбда-функции в Java все-таки появились, их реализация в Kotlin имеет меньше ограничений.
Одно из ключевых отличий лямбда-функций в 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. А что по этому поводу думаете вы? Делитесь своим мнением в комментариях.
Комментарии (18)
Fancryer
01.04.2024 11:29+3Про дата-классы не согласен, в Java вполне себе есть рекорды. Про потоки тоже было странно, поскольку виртуальные появились как JEP в JDK 19, а сейчас они уже стали частью стандарта. Почему бы не сравнивать Kotlin 1.9.22-1.9.23 с Java 22?
arty79 Автор
01.04.2024 11:29Основная мысль текста как раз в том, что и там, и там появились одни и те же фичи. Просто в разное время. По итогу Kotlin сегодня уже в принципе отвязан от JVM, и сравнивать их напрямую особого смысла нет. Неправильно же сравнивать Python и Java только потому, что оба языка объектно-ориентированные.
Речь про то, что, появившись в нужный момент как улучшенная версия Java, Kotlin смог занять свое место под солнцем - и тогда его преимущество в функциях оказалось решающим.ris58h
01.04.2024 11:29+3Но с первого же слайда вы про фичу в Java умалчиваете и утверждаете что "в Java для тех же целей необходимо подключать внешнюю библиотеку или писать лишний код", которое ложно уже несколько лет как.
BugM
01.04.2024 11:29+1Тогда сравнение в пользу Джавы может получиться. Рекорды, паттернматчинг и виртуальные потоки закрывают большую часть того что продавало Котлин. Разнообразные нотнулл аннотации закрывают остаток. Вот бы еще все основные бибилиотеки ими разметили…
Sigest
01.04.2024 11:29+3Да не то только null protection фишка котлина. Функциональное программирование в котлин в разы удобнее. Эти collection.stream()…..collect(…) в java просто ужас какой многословный по сравнению с collection.map{…} А если взять функции-расширения, перегрузка операторов, всякие делегаты с синглтонами, то и получается, что джаве еще далеко до удобства котлина. Сам пишу по работе на обоих языках примерно 50/50, не хейтю джаву, но такое вот субъективное мнение
ruslun
01.04.2024 11:29У меня вопрос к опытным разработчикам: цель стать разработчиком на Kotlin, надо сначала хорошо изучить Java или можно сразу браться за KT ?
arty79 Автор
01.04.2024 11:29Думаю, если есть практика владения другим объектно-ориентированным языком, то можно начать сразу с Kotlin; если же такой практики нет, то, возможно, лучше начать погружаться с Java
LordDarklight
01.04.2024 11:29Начать с Kotlin как раз проще, чем с Java не имея практики ООП.
Вот, с С++ безусловно проще на Java перейти, но ещё лучше - перейти на C# - а потом уже, по необходимости, на Kotlin
m0rtis
01.04.2024 11:29Если цель - стать разработчиком на Котлин, то изучать Java совершенно необязательно. Если будете писать под jvm-таргет, лучше просто изучить отдельно именно особенности jvm.
Sigest
01.04.2024 11:29Я бы выучил джаву. Взаимодействовать, скорее всего, придется. Ну там полазить в кишках библиотек и т.д. А идеоматически Котлин все дальше и дальше от Джавы в плане стиля кода. Я раньше иногда понять не мог на каком языке написан код - на джаве или котлине. Сейчас, если писать в стиле Котлин, как-то уже и совсем не Джава стиль получается.
DenSigma
01.04.2024 11:29Сравните пожалуйста с последней версией Java. С рекордами в частности. Какой бойлерплейт?
Второе - не понятен смысл маски в конструкции со значением по умолчанию. Код может быть простейший
private static class cl { private final int DEFAULT_X = 42; private void foo(int x) { System.out.println(x); } private void foo() { foo(DEFAULT_X); } }
Вообще, по опыту работы с Delphi, ничего хорошего в указании значений параметров по умолчанию в объявлении методов нет. Это протаскивание деталей реализации в объявление. Это раз. Второе (я не в курсе) - как эти параметры по умолчанию стыкуются с интерфейсами? Объявление методов должно быть ТОЧНО соответствовать объявлениям интерфейсов - в этом смысл интерфейсов. А в разных классах в объявлениях одних и тех-же методов могут быть разные константы. Это нарушение идеологии интерфейсов. В третьих, это просто путаница, когда в одних случаях нужно использовать параметры по умолчанию и свои параметры, а в других случаях - другие параметры по умолчанию и свои параметры. Это невозможно разрулить, когда параметров штук пять-десять. Java с помощью библиотек предлагает изящное решение - билдеры (правда надо определять класс параметров).
HemulGM
01.04.2024 11:29С интерфейсами все там стыкуется, если речь о Делфи. Для интерфейса такой метод - это метод с набором всех параметров и не важно, что какие-то заданы по умолчанию.
Параметры по умолчанию достаточно редко используются, но это все равно удобно.
Кстати, в Делфи в конструкторе я часто использую вариант такой:
constructor TMyObject.Create(Proc<TMyObject>); begin inherited Create; Proc(Self); end; // и при вызове var Obj := TMyObject.Create( procedure(MyObj: TMyObject) begin MyObj.Prop1 := 1; end)
Sigest
01.04.2024 11:29Во-первых, В Java и Kotlin интерфейсы необязательны. В Kotlin даже классы необязательны (хотя в байткоде обернутся все же в классы функции и переменный объявленые вне классов). Поэтому метод с параметрами необязательно должен стыковаться с каким-то там интерфейсом. Даже если у класса, в котором объявлен метода с параметрами по умолчанию, есть интерфейс - ну и ладно. Я лично так делал и не страдал от нарушения идеологий. Да и не понимаю я, в чем нарушение? Интерфейс - это контракт на структуру и результат, а не на данные или способ подкапотного функционирования этой структуры. Данные как раз предполагаются быть разными в имплементациях, раз уж интерфейс объявлен.
Во-вторых, это плохо иметь 10 параметров в методе. И в Котлине для таких случаев тоже надо использовать билдеры, хоть самому писать, хоть с использованием тех же библиотек из Джавы
pokrovskiy_199
Я заметил ,что одна из основных особенностей, которая сразу бросается в глаза, - это безопасность от нулевых ссылок. В Kotlin вы пишете менее подверженный ошибкам код благодаря строгой системе типов, которая обеспечивает безопасность от нулевых ссылок. Но это также означает, что в байткоде появляются дополнительные проверки null для обеспечения безопасности, что в некоторых случаях может сказаться на производительности.
koperagen
Если рантайм проверки действительно сказываются на производительности какого-то конкретного кода, то можно сделать функцию приватной. Ее можно будет вызвать только из котлин кода, где компилятор просто не даст передать нулл там где не надо. Тогда рантайм проверки для параметров функции не нужны и не будут сгенерированы