Что такое kotlin и с чем его едят: обучающее руководство и сравнение нового языка android-разработки с java
Содержание:
Мотивация
В Java мы привыкли к классам с названием «*Utils»: , и т.п. Довольно известным следствием этого является . Но вот использование таких утилитных классов в своём коде — не самое приятное занятие:
Имена таких классов постоянно используются при вызове. Мы можем их статически импортировать и получить что-то типа:
Уже немного лучше, но такой мощный инструмент IDE, как автодополнение, не предоставляет нам сколь-нибудь серьёзную помощь в данном случае. Намного лучше, если бы у нас было:
Но мы же не хотим реализовывать все возможные методы внутри класса , ведь так? Вот для чего и нужны расширения.
Требования к делегированным свойствам
Здесь приведены требования к объектам-делегатам.
Для read-only свойства (например val), делегат должен предоставлять функцию , которая принимает следующие параметры:
- — должен иметь такой же тип или быть наследником типа хозяина свойства (для расширений — тип, который расширяется)
- — должен быть типа или его родительского типа. Эта функция должна возвращать значение того же типа, что и свойство (или его родительского типа).
Для изменяемого свойства (var) делегат должен дополнительно предоставлять функцию , которая принимает следующие параметры:
- — то же что и у ,
- — то же что и у ,
- new value — должен быть того же типа, что и свойство (или его родительского типа).
Функции и/или могут быть предоставлены либо как члены класса-делегата, либо как его расширения. Последнее полезно когда вам нужно делегировать свойство объекту, который изначально не имеет этих функций. Обе эти функции должны быть отмечены с помощью ключевого слова .
Эти интерфейсы объявлены в стандартной библиотеке Kotlin:
Translation Rules
Для каждого делегированного свойства компилятор Kotlin «за кулисами» генерирует вспомогательное свойство и делегирует его. Например, для свойства генерируется скрытое свойство , и исполнение геттеров и сеттеров просто делегируется этому дополнительному свойству:
Компилятор Kotlin предоставляет всю необходимую информацию о в аргументах: первый аргумент ссылается на экземпляр внешнего класса и reflection-объект типа , описывающий сам .
Заметьте, что синтаксис для обращения к напрямую в коде программы доступен только с Kotlin версии 1.1
Предоставление делегата
Примечание: Предоставление делегата доступно в Kotlin начиная с версии 1.1
С помощью определения оператора вы можете расширить логику создания объекта, которому будет делегировано свойство. Если объект, который используется справа от , определяет как член или как расширение, эта функция будет вызвана для создания экземпляра делегата.
Один из возможных юзкейсов — это проверка состояния свойства при его создании.
Например, если вы хотите проверить имя свойства перед связыванием, вы можете написать что-то вроде:
имеет те же параметры, что и :
- — должен иметь такой же тип, или быть наследником типа хозяина свойства (для расширений — тип, который расширяется)
- — должен быть типа или его родительского типа. Эта функция должна возвращать значение того же типа, что и свойство (или его родительского типа)
Метод вызывается для каждого свойства во время создания экземпляра , и сразу совершает необходимые проверки.
Не будь этой возможности внедрения между свойством и делегатом, для достижения той же функциональности вам бы пришлось передавать имя свойства явно, что не очень удобно:
В сгенерированном коде метод вызывается для инициализации вспомогательного свойства .
Сравните сгенерированный для объявления свойства код со сгенерированным кодом из Transaction Rules (когда не представлен):
Заметьте, что метод влияет только на создание вспомогательного свойства и не влияет на код, генерируемый геттером или сеттером.
Операторы и специальные символы (Operators and Special Symbols)
Котлин поддерживает следующие операторы и специальные символы:
- , , , , — математические операторы
-
- оператор присваивания
- используется для
- , , , , —
- , —
- , , — логические операторы ‘и’, ‘или’, ‘не’ (для побитовых операций используют соответствующие )
- , — (переведенные на вызовы для не-примитивных типов)
- , —
- , , , — (переведенные на вызовы для не-примитивных типов)
- , — (переведенный на вызовы и )
- выполняет (вызывает метод или обращается к свойству, если получатель не имеет значения null)
- принимает правое значение, если левое значение равно нулю ()
- создает или
- создает диапазон
- отделяет имя от типа в объявлениях
- отмечает тип с
-
- разделяет параметры и тело
- разделяет параметры и тип возвращаемого значения
- разделяет условие и тело ветви
-
- вводит
- вводит или ссылается на
- вводит или ссылается на
- ссылается на
- ссылается на
- разделяет несколько операторов на одной строке
- ссылается на переменную или выражение в
-
- заменяет неиспользуемый параметр в
- заменяет неиспользуемый параметр в
Учебный проект
В ходе обучения мы будем активно использовать проект «Котлин как первый язык программирования», содержащий текст данного пособия и около сотни различных задач на языке Kotlin. Оригинальный код данного проекта доступен по адресу https://github.com/Kotlin-Polytech/KotlinAsFirst2018 на сайте GitHub, который является специализированным хранилищем программных кодов и основан на системе контроля версий Git. Для того, чтобы начать работать с этим проектом, Вам необходимо выполнить следующие действия.
- Зарегистрироваться на https://github.com/ (в случае, если у Вас еще нет GitHub аккаунта). Далее выбранное Вами имя будет обозначаться как <USERNAME>.
- Создать специальную копию репозитория проекта — форк. Для этого достаточно зайти на страницу проекта https://github.com/Kotlin-Polytech/KotlinAsFirst2018 и нажать кнопку в правом верхнем углу страницы. После этого Ваша персональная копия проекта станет доступна по адресу https://github.com/<USERNAME>/KotlinAsFirst2018, и всю работу по решению различных задач Вы должны выполнять именно с Вашей копией.
- Для загрузки проекта в IntelliJ IDEA следует выполнить команду → из окна (или → → → из окна проекта), в появившемся окне ввести Git Repository URL https://github.com/<USERNAME>/KotlinAsFirst2018 и место на компьютере, куда будет скачан проект (Parent Directory).
- Далее следуйте инструкциям среды для настройки проекта. Подробное руководство вы можете найти здесь.
Проект содержит задачи, разбитые на девять уроков (lesson). Тексты задач доступны через окно в IntelliJ IDEA (открывается комбинацией клавиш ). В папках , где X — номер урока, находятся примеры решённых задач к данному уроку, тексты задач, которые необходимо решить, и готовые заглушки функций для написания решения. В папках test/lessonX находятся тестовые функции к задачам. Подробнее о задачах и тестах см. раздел 1 этого пособия.
Вопросы
В ходе изучения нового языка у вас, конечно, будут возникать вопросы, не стесняйтесь их задавать. У вас есть следующие возможности:
- посмотреть «часто задаваемые вопросы» далее по тексту
- поискать ответ на вопрос с помощью поисковой системы в Интернете
- почитать разнообразную информацию о Котлине в его документации
- русскоязычная документация по Котлину здесь
- задать нам вопрос в Kotlin Slack (получить приглашение можно здесь) в канале russian-kotlinasfirst
- воспользоваться другими ресурсами для общения
Kotlin Slack — это система общения, созданная специально для программистов на Котлине. Система состоит из множества каналов, посвящённых разным аспектам программирования на Котлине — в большинстве из них общение идёт на английском языке. Нашему курсу посвящён канал russian-kotlinasfirst, и там вы сможете задать любые вопросы по этому курсу на русском языке. В качестве других важных каналов имеется general — канал с общими обсуждениями, касающимися Котлина, и russian — общий канал для русскоязычных Котлин-программистов.
Часто задаваемые вопросы (F.A.Q.)
Что делать, если при открытии файла расширением .kt из учебного проекта (например, Simple.kt) вы видите сообщение над ним Project SDK is not defined?
Нажмите на ссылку в правой части сообщения. Выберете JDK 1.8 для работы с проектом в появившемся окне. Если список JDK в окне пуст или не содержит JDK 1.8, следует нажать на клавишу , затем зелёный плюс в верхнем левом углу и зарегистрировать установленную на Вашем компьютере JDK 1.8 в Intellij IDEA. Если Вы забыли установить JDK, это следует сделать, предварительно скачав её с сайта Oracle.
Что делать, если отсутствует зелёный треугольник напротив функции main и тестовых функций?
Откройте окно Maven Projects на панели в правой части окна IDEA (если вы не видите там такой надписи, откройте его через меню — View > Tool Windows > Maven Projects) и нажмите в нём на кнопку с изображением двух стрелок в круге. Дождитесь окончания импортирования Maven-проекта (наблюдайте за надписями в нижней части окна IDEA), после чего зелёные треугольники должны появиться. Проверьте также отсутствие надписи в верхней части окна (см. вопрос выше).
Если вам не удаётся открыть окно Maven Projects, попробуйте выйти из Intellij IDEA и войти в неё заново.
Также предлагаем Продвинутые курсы:
- Продвинутый курс по разработке android-приложения «Чат-мессенджер» с push-уведомлениями на Kotlin
- Продвинутый курс по разработке android-приложения «ТОП-100 криптовалют» на Котлин
- Как создать простое мобильное приложение для любого сайта (с push-уведомлениями) на Kotlin
- Продвинутый курс по созданию андроид-приложения QuizApp (викторина, тест, экзамен, опрос)
- Продвинутый курс по разработке андроид-приложения для Twitter и Facebook
- Продвинутый курс по созданию андроид-приложения для сайта с push-уведомлениями
- Продвинутый курс по разработке андроид-приложения HD Обои с покупками в приложении
- Продвинутый курс по созданию андроид-приложения Вконтакте
- Как создать полноценное приложение-напоминалку на андроид
- Как создать мобильное приложение-клиент для YouTube
- Как создать игру Flappy Bird для андроид
- Создание игры для android Lunar Rover
Использованы материалы онлайн-курса «Котлин как первый язык программирования» в соответствии с лицензией
Strings
Strings are represented by the type . Strings are immutable.
Elements of a string are characters that can be accessed by the indexing operation: .
A string can be iterated over with a for-loop:
You can concatenate strings using the operator. This also works for concatenating strings with values of other types, as long
as the first element in the expression is a string:
Note that in most cases using or raw strings is preferable to string concatenation.
String literals
Kotlin has two types of string literals: escaped strings that may have escaped characters in them
and raw strings that can contain newlines and arbitrary text. Here’s an example of an escaped string:
Escaping is done in the conventional way, with a backslash. See above for the list of supported escape sequences.
A raw string is delimited by a triple quote (), contains no escaping and can contain newlines and any other characters:
You can remove leading whitespace with function:
By default is used as margin prefix, but you can choose another character and pass it as a parameter, like .
String templates
String literals may contain template expressions, i.e. pieces of code that are evaluated and whose results are concatenated into the string.
A template expression starts with a dollar sign ($) and consists of either a simple name:
or an arbitrary expression in curly braces:
Templates are supported both inside raw strings and inside escaped strings.
If you need to represent a literal character in a raw string (which doesn’t support backslash escaping), you can use the following syntax:
Функции
Чтобы помочь вам выбрать правильную функцию области видимости, ниже будет представлено их подробное описание и рекомендации по использованию. Во многих случаях они взаимозаменяемы, поэтому в примерах будет отражен общий стиль использования, а также соглашение по их применению.
Контекстный объект доступен в качестве аргумента (). Возвращаемое значение — результат выполнения лямбды.
Если значение переменной вычислялось при помощи цепочки операций, то позволяет использовать полученный результат для вызова одной или нескольких функций в блоке кода. Например, в следующем коде выполняется цепочка из двух операций, результат записывается в отдельную переменную, после чего она выводится на печать.
С функцией это код может быть переписан следующим образом:
Если блок кода содержит одну функцию, где является аргументом, то лямбда-выражение может быть заменено ссылкой на метод ():
часто используется для выполнения блока кода только с non-null значениями. Чтобы выполнить действия с non-null объектом, используйте оператор безопасного вызова совместно с функцией .
Еще один вариант использования — это введение локальных переменных с ограниченной областью видимости для улучшения читабельности кода. Чтобы определить новую переменную для контекстного объекта, укажите ее имя в качестве аргумента лямбды, чтобы ее можно было использовать вместо ключевого слова .
Не является функцией-расширением. Контекстный объект передается в качестве аргумента, а внутри лямбда-выражения он доступен как получатель (). Возвращаемое значение — результат выполнения лямбды.
Функцию рекомендуется использовать для вызова функций контекстного объекта без предоставления результата лямбды. В коде может читаться как» с этим объектом, сделайте следующее. «
Другой вариант использования — введение вспомогательного объекта, свойства или функции которые будут использоваться для вычисления значения.
Контекстный объект доступен в качестве получателя (). Возвращаемое значение — результат выполнения лямбды.
делает то же самое, что и , но вызывается как — как функция расширения контекстного объекта.
удобен, когда лямбда содержит и инициализацию объекта, и вычисление возвращаемого значения.
Помимо вызова для объекта-получателя, вы можете использовать его как функцию без расширения. В этом случае позволяет выполнить блок из нескольких операторов там, где это требуется.
Контекстный объект доступен в качестве получателя (). Возвращаемое значение — контекстный объект.
Используйте для такого блока кода, который не возвращает значение и в основном работает с членами объекта-получателя. Типичный способ использования функции — настройка объекта-получателя. Это всеравно что мы скажем “примени перечисленные настройки к объекту.”
Так как возвращаемое значение — это сам объект, то можно с легкостью включить в цепочки вызовов для более сложной обработки.
Контекстный объект доступен в качестве аргумента (). Возвращаемое значение — контекстный объект.
хорош для выполнения таких действий, которые принимают контекстный объект в качестве аргумента. То есть, эту функции следует использовать, когда требуется ссылка именно на объект, а не на его свойства и функции. Либо, когда вы хотите, чтобы была доступна ссылка на из внешней области видимости.
Когда вы видите в коде , то это можно прочитать как «а также с объектом нужно сделать следующее.»
Модификаторы (Modifier Keywords)
Следующие слова действуют как ключевые в списках модификаторов объявлений и могут использоваться как идентификаторы в других контекстах:
- означает реализацию Платформы в мультиплатформенных проектах
- обозначает класс или элемент как
- объявляет класс аннотации
- объявляет
- помечает свойство как
- запрещает
- указывает компилятору генерировать канонические элементы для класса
- объявляет перечисление
- отмечает объявление как платформенное, ожидая реализации в модулях платформы.
- отмечает объявление как реализованное не в Kotlin (доступное через или )
- запрещает
- позволяет вызывать функцию в
- указывает компилятору встроить функцию и лямбда-выражение на стороне вызова
- позволяет ссылаться на экземпляр внешнего класса из вложенного класса
- помечает объявление как видимое в текущем модуле
- позволяет инициализацировать
- отключает
- позволяет
- обозначает функцию как перегрузку оператора или реализацию соглашения
- обозначает тип параметра как
- помечает элемент как
- помечает объявление как видимое в текущем классе или файле
- помечает объявление как видимое в текущем классе и его подклассах
- помечает декларацию как видимую в любом месте
- обозначает параметр типа встроенной функции, как
- объявляет изолированный класс (класс с ограниченным подклассом)
- обозначает функцию или лямбда как приостанавливаемую (используется как сопрограмма)
- отмечает функцию как (позволяя компилятору заменить рекурсию итерацией)
- позволяет
❌ Недостатки
Низкая скорость. Чаще всего разработчики жалуются на непредсказуемую скорость компиляции. По быстродействию Kotlin уступает Java, поскольку в его основе лежит виртуальная машина JVM — фундаментальная программа, выпущенная специально под язык Java, а не под Kotlin.
Маленькое сообщество и единственный владелец языка. Kotlin всё ещё не такой популярный, как Java. Причина в том, что Kotlin — это не продукт Гугл. Разработчики боятся, что через какое-то время Гугл откажется от него, придумает какую-то свою версию языка или поссорится с JetBrains.
Малочисленное сообщество тормозит развитие Kotlin: под него медленно выпускаются новые библиотеки и обновления, а для решения технических проблем нужно обращаться в баг-трекер — написать в техподдержку JetBrains, добавить свою проблему в очередь задач, ждать и надеяться на её исполнение.
В сентябре 2020 в баг-трекере Kotlin около 40 000 задач, которые закрываются по мере критичности. До некоторых задач очередь доходит через несколько лет, но есть и те, что остаются нерешёнными — в таких условиях разработчики вынуждены искать костыльные решения или переходить на другой язык с развитым сообществом.
С 2002 года популярность Java постепенно снижается, однако по-прежнему на высоком уровне
Блокирование против приостановки
Главным отличительным признаком сопрограмм является то, что они являются вычислениями, которые могут быть приостановлены без блокирования потока (вытеснения средствами операционной системы)
Блокирование потоков часто является весьма дорогостоящим, особенно при интенсивных нагрузках: только относительно небольшое число потоков из общего числа является активно выполняющимися, поэтому блокировка одного из них ведет к затягиванию какой-нибудь важной части итоговой работы
С другой стороны, приостановка сопрограммы обходится практически бесплатно. Не требуется переключения контекста (потоков) или иного вовлечения механизмов операционной системы. И сверх этого, приостановка может гибко контролироваться пользовательской библиотекой во многих аспектах: в качестве авторов библиотеки мы можем решать, что происходит при приостановке, и оптимизировать, журналировать или перехватывать в соответствии со своими потребностями.
Еще одно отличие заключается в том, что сопрограммы не могут быть приостановлены на произвольной инструкции, а только в так называемых точках остановки (приостановки), которые вызываются в специально маркируемых функциях.
Платформо-зависимые объявления
Одна из ключевых возможностей мультиплатформенного кода Kotlin — способ организации зависимости
общего кода от платформенного. В других языках используется методика создания набора интерфейсов в общем коде и их
реализация в платформенном. Однако, этот подход не идеален в тех случаях, когда у вас есть библиотека
для целевой платформы, имеющая нужную вам функциональность, и вы хотите использовать API этой библиотеки напрямую,
без дополнительных обёрток. Также, вы будете вынуждены представлять все общие объявления в виде интерфейсов,
что не всегда применимо.
Kotlin же имеет механизм ожидаемых и актуальных объявлений.
С этим механизмом общий модуль может иметь ожидаемые объявления, а платформенный модуль — актуальные объявления,
соответствующие ожидаемым.
Чтобы понять, как это работает, взглянем на пример. Отрывок кода общего модуля:
Код соответствующего платформенного JVM-модуля:
Пример описывает несколько важных моментов:
- Ожидаемые объявления в общих модулях и соответствующие им актуальные объявления всегда имеют абсолютно одинаковые
имена. - Ожидаемые объявления помечены ключевым словом , а актуальные — .
- Все актуальные объявления, соответствующие любому ожидаемому объявлению,
должны быть помечены ключевым словом . - Ожидаемые объявления никогда не реализуются в общем модуле.
Заметьте, что не только интерфейсы и их члены могут быть помечены как ожидаемые.
В этом примере ожидаемый класс имеет конструктор, и его объекты могут быть созданы прямо из общего кода.
Вы также можете применять модификатор к объявлениям на верхнем уровне и аннотациям:
Компилятор следит за тем, чтобы каждое ожидаемое объявление имело соответствующее актуальное объявление
во всех платформенных модулях, реализующих соответствующий общий модуль, и сообщает об ошибке если какие-либо
актуальные объявления отсутствуют. IDE имеет средства, которые могут помочь создать отсутствующие ожидаемые объявления.
Если у вас есть платформенная библиотека, которую вы хотите использовать в общем коде и одновременно иметь собственную
реализацию для другой платформы, вы можете создать псевдоним для существующего класса как актуальное объявление:
Стандартные API
Сопрограммы представлены в трёх их главных ингредиентах:
- языковая поддержка (функции остановки, как описывалось выше),
- низкоуровневый базовый API в стандартной библиотеке Kotlin,
- API высокого уровня, которые могут быть использованы непосредственно в пользовательском коде.
Низкий уровень API: kotlin.coroutines
Низкоуровневый API относительно мал и должен использоваться ТОЛЬКО для создания библиотек высокого уровня. Он содержит два главных пакета:
- — главные типы и примитивы, такие как:
- — встроенные функции еще более низкого уровня, такие как
Более детальная информация о использовании этих API может быть найдена здесь.
API генераторов в kotlin.coroutines
Это функции исключительно «уровня приложения» в :
Они перенесены в рамки , поскольку они относятся к последовательностям. По сути, эти функции (и мы можем ограничиться здесь рассмотрением только ) реализуют генераторы, т. е. предоставляют лёгкую возможность построить ленивые последовательности:
Это сгенерирует ленивую, потенциально бесконечную последовательность Фибоначчи, используя сопрограмму, которая дает последовательные числа Фибоначчи, вызывая функцию yield (). При итерировании такой последовательности на каждом шаге итератор выполняет следующую часть сопрограммы, которая генерирует следующее число. Таким образом, мы можем взять любой конечный список чисел из этой последовательности, например , дающий в результате . И сопрограммы достаточно дёшевы, чтобы сделать это практичным.
Чтобы продемонстрировать реальную ленивость такой последовательности, давайте напечатаем некоторые отладочные результаты изнутри вызова sequence():
Запустите приведенный выше код, чтобы убедиться, что если мы будем печатать первые три элемента, цифры чередуются со -ами по ветвям цикла. Это означает, что вычисления действительно ленивые. Для печати мы выполняем только до первого и печатаем по ходу дела. Затем, для печати , нам необходимо переходить к следующему , и здесь печатать . То же самое и для . И следующий никогда не будет напечатан (точно так же как и ), поскольку мы никогда не запрашиваем дополнительных элементов последовательности.
Чтобы сразу породить всю коллекцию (или последовательность) значений, доступна функция :
Функция во всём подобна sequence(), но только возвращает ленивый итератор.
Вы могли бы добавить собственную логику выполнения функции , написав приостанавливаемое расширение класса (что порождается аннотацией , как описывалось ):
Другие API высокого уровня: kotlinx.coroutines
Только базовые API, связанные с сопрограммами, доступны непосредственно из стандартной библиотеки Kotlin. Они преимущественно состоят из основных примитивов и интерфейсов, которые, вероятно, будут использоваться во всех библиотеках на основе сопрограмм.
Большинство API уровня приложений, основанные на сопрограммах, реализованы в отдельной библиотеке . Эта библиотека содержит в себе:
- Платформенно-зависимое асинхронное программирование с помощью :
- этот модуль включает Go-подобные каналы, которые поддерживают и другие удачные примитивы
- исчерпывающее руководство по этой библиотеке доступно здесь.
- API, основанные на из JDK 8:
- Неблокирующий ввод-вывод (NIO), основанный на API из JDK 7 и выше:
- Поддержка Swing () и JavaFx ()
- Поддержка RxJava:
Эти библиотеки являются удобными API, которые делают основные задачи простыми. Также они содержат законченные примеры того, как создавать библиотеки, построенные на сопрограммах.
Отличия от Java
Классы данных (Data Classes)
В Kotlin появились специальные классы, предназначенные специально для хранения данных. Они генерируют различные шаблоны: , , , геттеры и сеттеры и т.д. Сравните код на Java:
И на Kotlin:
Легко создавать копии классов данных при помощи метода :
Функции-расширения
Kotlin позволяет расширять функциональность существующих классов, не прибегая к наследованию. Это делается при помощи функций-расширений. Для объявления такой функции к её имени нужно приписать префикс в виде расширяемого типа. Вот так можно добавить функцию в :
Ключевое слово this внутри функции-расширения относится к объекту-получателю, который передаётся перед точкой. Теперь мы можем применить функцию swap к любому изменяемому списку:
Умные приведения типов
Компилятор Kotlin очень умён, когда речь заходит о приведениях типов. В большинстве случаев не требуется явно указывать операторы приведения, поскольку в языке есть оператор , который делает за вас всю работу:
Функциональное программирование
Важно отметить, что Kotlin заточен под функциональное программирование. Он предоставляет большое количество полезных возможностей, например, функции высшего порядка, лямбда-выражения, перегрузку операторов и ленивые вычисление логических выражений
Вот пример работы с коллекциями:
Функции высшего порядка — это функции, которые принимают другие функции в качестве аргументов и возвращают функции. Рассмотрим следующий пример:
В нём — это имя аргумента, а — это тип функции. Мы говорим, что будет функцией, не принимающей аргументов и ничего не возвращающей.
Лямбда-выражения, или анонимные функции — это функции, которые не объявляются, а передаются в виде выражений. Вот пример:
Мы объявляем переменную , которая берёт два числа, складывает их и принимает значение суммы, приведённое к целому. Для вызова достаточно простого .
Сравнение скорости Java и Kotlin
Первая сборка Kotlin-кода занимает примерно на 15–20% больше времени, чем аналогичный процесс на Java. Однако инкрементная сборка Kotlin даже немного быстрее, чем у Java. Таким образом, языки примерно равны по скорости компиляции.