Предлагаю начать с общих черт.

  • Статическая типизация

  • Бесплатны и с открытым кодом

  • Код преобразуется в байт-код

  • Интероперабельность

  • Объектно-ориентированные языки программирования

1. Статическая типизация

Java и Kotlin являются языками со статической типизацией. Это означает, что проверка типов выполняется во время компиляции. (Существуют также языки с динамической типизацией, например PHP, Python и JavaScript.)

Это хорошо видно из примера ниже.

2. Бесплатны и с открытым кодом

Оба языка являются бесплатными и распространяются как открытое программное обеспечение (можно использовать без ограничений и участвовать в развитии).

3. Байт-код

Оба языка преобразуют код в байт-код, исполняемый JVM.

4. Интероперабельность

Оба языка являются интероперабельными. Это означает, что файлы Java и Kotlin могут сосуществовать в одном проекте или пакете JAR.

5. Поддержка ООП

Оба являются объектно-ориентированными языками программирования.

(Таким образом, они поддерживают основные концепции ООП)

Полиморфизм, наследование, инкапсуляция, абстракция.

А теперь рассмотрим основные отличия.

1. Представление публике и релиз

  • Язык Java был разработан компанией Sun Microsystems (в настоящее время принадлежит Oracle) в 1995 году.

  • Он поддерживается практически всеми типами устройств и операционных систем, будь то Android, Windows или Linux.

  • Язык Kotlin был представлен компанией JetBrains в 2011 году, выложен в открытый доступ в 2012 году, официально поддержан на Google I/O (ежегодном мероприятии разработчиков Google) в 2017 году.

  • По заявлению Google, 70 % из 1000 лучших приложений для Android сейчас написаны на Kotlin.

  • Некоторые приложения пока находятся в процессе переноса с Java на Kotlin, например приложение Google Home еще не полностью переписано на Kotlin...

  • ...но по состоянию на июнь 2020 года около 30 % старой кодовой базы на Java уже было переписано на Kotlin.

  • Другими популярными примерами Kotlin-приложений от Google являются Maps, Play и Drive.

  • Прочие компании также выпустили множество приложений для Android, написанных на языке Kotlin.

  • Сейчас при поддержке разработки под Android компания Google придерживается стратегии «Kotlin прежде всего». Это в чем-то похоже на ситуацию с разработкой приложений под iOS, где произошел переход от Objective-C к Swift.

2. Версия

  • По состоянию на ноябрь 2020 года актуальной версией Kotlin является 1.4.0.

  • Несмотря на выход Java 15, самой популярной версией все еще остается Java 8 (или 1.8).

3. Скорость

  • По этому параметру Java превосходит Kotlin на 12–15 % для чистых сборок (то есть в этом случае Kotlin компилируется немного медленнее).

  • Однако в случае частичных сборок с включенной инкрементной компиляцией (то есть с компиляцией только небольших изменений) Kotlin компилируется так же быстро, как и Java, или даже немного быстрее.

4. Количество строк кода

  • Код, написанный на Kotlin, намного компактнее по сравнению с Java — на 30–40 %.

    Таким образом, в теории размер приложений может уменьшиться на треть.

  • Язык Java — крайне многословный, а Kotlin — лаконичный и современный.

5. Доля рынка

  • По данным опросов, разработчиков на Kotlin примерно в пять раз меньше, чем использующих Java.

  • 7,8 % разработчиков предпочитают Kotlin, более чем 40 % — Java. Однако эти опросы также показывают, что Kotlin нравится программистам больше, чем Java, и быстро наращивает аудиторию.

Ссылки

6. Безопасная работа с обнуляемыми переменными (null safety)

  • Kotlin защищен от NullPointerException. Именно этот тип ошибки является самой частой причиной сбоев приложений из Google Play.

  • Java позволяет разработчикам присваивать значение null любой переменной.

  • В отличие от Java, в Kotlin по умолчанию все типы являются non-nullable, то есть не могут принимать значение null. Присвоение или возврат null приведет к ошибке компиляции.

  • Чтобы присвоить переменной значение null, в Kotlin необходимо явно пометить эту переменную как nullable.

val number: Int? = null //Nullable type
val name: String = null //Error because not possible to assign a null value

Nullable-типы используются с оператором безопасного вызова.

name?.getLength()

Таким образом, даже если name примет значение null, все выражение будет эквивалентно null без возникновения NullPointerException.

7. Гибридные приложения

  • Kotlin можно использовать для написания нативных приложений для Android и iOS.

  • Kotlin Multiplatform Mobile (KMM) работает в Android и iOS.

  • Java до последнего времени не использовалась при разработке приложений под iOS.

Теперь рассмотрим СТИЛЕВЫЕ отличия.

  1. Функция main

  • В Java метод main должен размещаться внутри класса. Он объявляется как статический метод.

  • В Kotlin, чтобы сделать функцию статической, достаточно поместить ее на уровень пакета. Так что это может быть самостоятельная функция без класса.

  • Аргументы метода main в Kotlin можно опустить, если наша программа не должна принимать аргументы командной строки.

fun main(args : Array<String>) {
println(“hello”)
}
  • В Java, если мы не включаем аргументы в функцию main (даже если мы их не используем), выводится ошибка.

Error: Main method not found in class

2. Поведение по умолчанию

  • В отличие от Java, по умолчанию классы в Kotlin являются финальными (final), поэтому, чтобы разрешить наследование от класса, его следует пометить ключевым словом open.

  • Чтобы разрешить переопределение метода, его необходимо явно пометить как open.

class A { … } in Java is equal to open class A { … } in Kotlin.
final class B { … } in Java is equal to class B { …} in Kotlin.
  • В Kotlin все, что не имеет модификаторов доступа, по умолчанию является public. Мы можем явно прописать public в определении, но это не обязательно.

public class A { … } и class A { … }

— это одно и то же в Kotlin.

В Kotlin используется четыре модификатора видимости: private, protected, public и internal.

Internal подразумевает повсеместную видимость в одном модуле.

  • В Java используется ключевое слово default.

  • Ключевое слово default является модификатором доступа. Если вы не назначили переменным, методам, конструкторам и классам какой-либо модификатор доступа, то по умолчанию используется модификатор доступа default.

  • Default обеспечивает видимость в пределах пакета.

  • Методы с модификатором default позволяют добавлять в интерфейсы новые функциональные возможности.

interface AnInterface {
public default void myMethod() {
System.out.println(“D”);
}
} /Allowed
  • В Kotlin ключевое слово default не используется.

3. Типы данных и массивы

  • Изменяемым (mutable) и неизменяемым (immutable) типами в Kotlin являются var и val

//Компилятор может определить тип переменной по ее значению.

var website = “hello”
var website: String = “hello” //одно и то же

//Указывать тип обязательно, если вначале идет объявление, а потом инициализация

var website: String
website = “hello“
  • В числовых литералах разрешается использовать символы подчеркивания.

val creditCardNumber = 1234_5678_9012_3456L

Нельзя сравнивать типы разной величины.

val a: Int = 10; val b: Long = 10L

print(a == b) // Ошибка в Kotlin: сравнение невозможно. В Java возвращается true.
  • Имена примитивных типов данных в Kotlin начинаются с заглавной буквы, например Boolean и Int, в то время как в Java — со строчной, например char и double.

  • Классы-обертки, такие как Integer, доступны в обоих языках.

val num:Integer = Integer(10) //корректный код в Kotlin
val num:Integer = 10 //не будет работать

Массивы объявляются следующим образом:

Int[] numbers = new int[] {10,20,30,40,50}
val numbers = intArrayOf(10,20,30,40,50)
val numbers = arrayOf(10,20,30,40,50)
var numbers = IntArray(5){it*10}
var numbers = Array<Int>(5){it*10}

4. Списки

Тип List по умолчанию в Kotlin является неизменяемым, поэтому методы add() или remove() работают не так, как в Java.

val lst = listOf<Int>(10, 20, 30, 40, 50)
lst.add(60) //Ошибка
val lst2 = mutableListOf<Int>(10, 20, 30, 40, 50) //то же самое, что ArrayList<Int>
// val для mutableList? Да, потому что нового присваивания не происходит, только изменение содержимого.
lst2.add(60) //OK
lst2 += 70 //тоже OK

Оперировать списком в языке Kotlin можно с помощью функций take и drop.

val nums = listOf(0,1,2,3,4,5,6,7)
nums.take(3) // [0,1,2]
nums.drop(3) // [3,4,5,6,7]

5. Циклы

В Kotlin доступно несколько вариантов циклов for. В Java последний имеет фиксированную структуру.

val lst : List<Int> = listOf<Int>(10, 20, 30, 40, 50)
for(item in lst){
println(item)
}
for(item in 0 until lst.size){
println(lst[item])
}
for(item in 0..4){ //оператор диапазона (range)
println(lst[item])
}

В заключение предлагаю рассмотреть функциональные различия.

  1. Конструкторы

  • В Kotlin доступны два вида конструкторов.

  • Один из них прописывается после имени класса и называется первичным конструктором, а второй прописывается в теле класса и называется вторичным конструктором.

  • В классе могут быть один первичный конструктор и несколько вторичных.

Первичный конструктор не может содержать код. Код инициализации можно поместить в блок init.

fun main(args: Array<String>) {
val per1 = Person(“amir”) //вызов только первичного конструктора
val per2 = Person(“ansari”,20, ‘A’) //если убрать ‘A’ , вторичный конструктор не вызывается, //так как нет значения по умолчанию для blood_group
}

Вторичный конструктор должен расширять поведение первичного конструктора.

class Person (var name: String, var age: Int = 18) {
init{
println(“Student has got a name as $name and age as $age”)
}
var blood_group: Char = ‘O’
constructor(_name: String, age: Int, blood_group: Char) : this(_name, age) {
this.blood_group = blood_group
println(“Student name= $_name and age= $age and blood group=$blood_group”)
}
}

2. Функции-расширения

  • Kotlin позволяет разработчикам расширять класс, добавляя новую функциональность при помощи функций-расширений.

  • Это поистине замечательное улучшение, поскольку программисту не требуется расширять класс.

  • По сути, функция-расширение — это функция, которая является членом класса, но определена за его пределами.

  • В Java функций-расширений не было.

fun String.countSpaces(): Int {
return this.count { c -> c == ‘ ‘ }
}

3. Функции высшего порядка

В языке Kotlin функция, которая может принимать в качестве параметра функцию или лямбда-выражение или же может возвращать функцию, называется функцией высшего порядка (higher-order function).

// лямбда-выражение
var lambda = {a: Int , b: Int -> a + b }
//функция высшего порядка
fun highfun( lmbd: (Int, Int) -> Unit) { // принимает лямбда-выражение как параметр, ничего не возвращает
var result = lmbd(2,4) // вызывает лямбда-выражение, передавая ему параметры
println(“Сумма двух чисел равна $result”)
}
fun main() {
highfun(lambda) //лямбда-выражение передается как параметр
}

4. Data-классы

  • В больших проектах, как правило, используется несколько классов, предназначенных исключительно для хранения данных.

  • Разработчику на Java приходится писать много стандартного, но часто встречающегося кода (так называемый шаблонный код или boilerplate), data-классы в Kotlin позволяют избежать этих дополнительных усилий.

  • В Java-классе для этой цели должны присутствовать геттеры и сеттеры, функции Hashcode(), toString() и equals(). Эквивалентом в Kotlin будет...

data class Person(var name: String, var surname: String, var id: String)

— это все!

5. Статические члены

Как только мы объявляем переменную static, она загружается в память во время компиляции, то есть доступна только одна ее копия. Одиночки (signgleton) и статические члены имеют схожее поведение.

Ключевое слово static делает компонент частью класса, не связанной с объектом этого класса.

В концепции ООП то, что не является объектом, существовать не должно.

В Java все должно объявляться внутри класса. Но в Kotlin все иначе. Компоненты могут объявляться за пределами класса, и это автоматически делает их статическими. Поэтому нам не требуется ключевое слово static.

В Java статические члены обрабатываются не так, как члены-объекты. Это означает, что для статических членов нам недоступны такие вещи, как реализация интерфейса, помещение экземпляра в ассоциативный список (map) или передача его в качестве параметра методу, который принимает объект.

В Kotlin static не является ключевым словом и вместо статических членов используются объекты-компаньоны, позволяющие преодолеть вышеуказанные ограничения.

  • В этом и заключается преимущество.

  • Даже если члены объектов-компаньонов выглядят как статические члены в других языках, во время выполнения они все равно остаются членами экземпляров реальных объектов и могут, например, реализовывать интерфейсы.

6. Асинхронная обработка

В Java существует множество решений для асинхронной работы: RxJava, AsyncTask (уже официально не поддерживается), обработчики, обратные вызовы...

Наряду со всеми этими возможностями в Kotlin также имеются корутины (coroutines, также их называют сопрограммами), которые упрощают работу.

Корутины (или легковесные потоки) не являются отдельными потоками, но несколько корутин могут совместно использовать один поток.

7. Проверяемые исключения

Такие исключения, как IOException и FileNotFoundException, присутствуют в Java, но не поддерживаются в Kotlin.

  • Причина в том, что они ничего не делают, кроме как содержат комментарий в блоке catch.

8. Ленивая загрузка

В Kotlin модификаторы lateinit и by Lazy позволяют инициализировать значения до их фактического использования.

val myUtil by lazy {
MyUtil(parameter1, parameter2)
}
lateinit var myUtil: MyUtil

Вышеприведенный код инициализирует объект MyUtil. Но это будет сделано только при первом использовании myUtil.

Оба варианта служат одной цели, но по факту сильно отличаются. Один является неизменяемым (val), другой — изменяемым (var).

@Injectlateinit var myUtil: MyUtil
  • Ключевое слово lateinit было специально введено в Kotlin для переменных внедрения зависимостей (DI). Его следует использовать для изменяемых или устанавливаемых извне значений. Это полезно, если мы не хотим инициализировать значение, но при этом хотим избежать проверки на null.

Чтобы проверить, было ли свойство, определенное как lateinit var, уже инициализировано, вызовите метод .isInitialized() для ссылки на это свойство:

if (foo::bar.isInitialized) {
println(foo.bar)
}

Переменная lateinit может быть инициализирована из любого места, откуда доступен объект.

В свою очередь, Java не поддерживает отложенную инициализацию, поэтому значения инициализируются, даже если они не используются.

Вот некоторые различия между этими двумя мощными языками.

Благодарю за внимание!


Материал подготовлен в рамках курса «Подготовка к сертификации Oracle Java Programmer (OCAJP)».

Всех желающих приглашаем на открытый урок «Конструкторы и блоки инициализации». На занятии мы:
- Разберём конструктор на запчасти;
- Определим финалистов (финальные переменные);
- Наведём порядок (инициализации).

→ РЕГИСТРАЦИЯ

Комментарии (15)


  1. koloritnij
    29.09.2021 19:27
    -4

    Функции-расширение - самое большое зло в Котлине, не могу придумать для них адекватного применения, но запутать очень легко.


    1. kernelconf
      29.09.2021 20:07
      +1

      Постоянно ими пользуюсь в пет проекте

      • при работе с БД через Exposed - есть своя библиотека со схемой и дата классами. Если использующий библиотеку проект что-то должен сделать специфическое с данными дата класса, я это оформляю через расширение

      • В ту же библиотеку, с БД и Exposed я постоянно добавляю что-то своё, чего в Exposed нет, но что достаточно типовое. Например, получение следующего числа последовательности, часто используемые функции и т.п.

      • Иногда в сторонней библиотеке надо что-нибудь доложить в private поле, я быстренько делаю расширение и через рефлексию докидываю.

      Очень удобный механизм. Жаль на работе не разрешают Котлин использовать (мотивируя это тем, что поддерживать некому будет)


      1. koloritnij
        29.09.2021 20:17
        +2

        но это все очень неочивидно и как это поддерживать. Ведь зайдя в класс ты ожидаешь там увидеть всю реализацию, а тут приходится собирать ее по разным местам. Рефлексия так вообще.. Бррр, это может работать на пет проекте, но в крупном проде - будет кошмар. Гораздо лучше форкануть либу или , накрайняк, самому написать, чем рефлексией или расширениями делать новый функционал


        1. kernelconf
          29.09.2021 21:41
          +1

          Ну да, случается. Я иногда находил несколько экземпляров одного и того же расширения у себя в разных местах. С другой стороны, а если бы я это добавлял в data class? Получился бы страшный класс, да ещё и библиотеку пришлось бы релизить чаще.

          Думаю, это вопрос привычки. Нашим джавистам и ломбоковский val не нравится, они это аргументируют почти как вы (заходя в класс, я ожидаю перед объявлением переменной увидеть её тип).


          1. ApeCoder
            30.09.2021 11:51
            +1

             Я иногда находил несколько экземпляров одного и того же расширения у себя в разных местах. 

            Это в какой-то степени лечится соглашением о наименовании файлов. При попытке создать второй экземпляр вы натыкаетесь на то, что файл с таким именем уже есть.


    1. Jacen11
      01.10.2021 00:51

      Для мапинга очень удобно. И зачем тебе внутри класса знать что кто-то твою сущность в свой объект переделывает? И не надо плодить классы маперы. Для списков в самом котлине море полезных экстеншенов, для стринги. Например метод orEmpty на нулабельных списке или строке вернёт пустой список или строку, а не налл

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


    1. koperagen
      04.10.2021 15:53

      Дизайн библиотек. Там как раз очень удобно отделять интерфейсы и их реализации от всяких побочных методов, нужных только для удобства API.


  1. elzahaggard13
    29.09.2021 20:15
    +2

    Обожаю котлин, но 70% статьи не сравнение котлина и джавы, а описание котлина


  1. aleksandy
    29.09.2021 21:42
    +11

    Код, написанный на Kotlin, намного компактнее по сравнению с Java — на 30–40 %.

    Таким образом, в теории размер приложений может уменьшиться на треть.

    ЛПП. Уменьшиться может размер исходных кодов, но никак не приложения. Байт-код-то будет +/- один и тот же.

    Java позволяет разработчикам присваивать значение null любой переменной.

    Опять неправда. Примитиву нельзя присвоить null.

    Изменяемым (mutable) и неизменяемым (immutable) типами в Kotlin являются var и val

    Это не типы, а способы объявления переменных и полей.

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

    Это вообще фича java с версии 1.5, емнип.

    Короче, сравнивается тёплое с мягким. Причём с упором на разработку мобильных приложений.


  1. vanxant
    30.09.2021 01:31

    Пункт 7 про исключения не понял, может кто пояснит?


    1. vlanko
      30.09.2021 23:46
      +1

      В котлине нет Checked Exceptions. Тех, кого нужно обязательно кетчить. Типа, потому что бесполезно.


  1. bsod_keks
    30.09.2021 03:50
    +6

    А откуда в котлине взялись примитивные тип, у которых:

    Имена примитивных типов данных в Kotlin начинаются с заглавной буквы, например Boolean и Int

    это реально блог компании которая обучает людей кодить за овер прайс?


    1. aleksandy
      30.09.2021 06:46
      +5

      Просто переводчикам нужно план выполнять, а перевод хорошей качественной статьи долог и с ходу не делается. Зато можно взять рандомного индуса с его блогом и фигачить по 100500 переводов в наносекунду.


      1. kacetal
        02.10.2021 11:24

        Странно это, разве это не создаёт антирейтинг этой школе.


  1. s-buvaka
    01.10.2021 08:30
    +1

    Сейчас бы в 2021 Java и Kotlin сравнивать...