В каком году появился язык программирования kotlin. О языке Kotlin для Java-программистов. Основные элементы языка

Официальным языком для разработки на Android.

Почему Kotlin назвали в честь острова в Финском заливе, как и когда язык стал популярен среди разработчиков мобильных приложений, почему им удобно пользоваться и зачем он изначально понадобился? Маркетинг-менеджер Kotlin Роман Белов рассказал «Бумаге» , как в Петербурге создавали язык, признанный Google.

Фото из архива Романа Белова

Как и когда появился язык программирования Kotlin?

Мы начали разрабатывать Kotlin в 2010 году. К тому времени компании JetBrains было уже десять лет и основной продукт компании - JetBrains IntelliJ IDEA, полностью написанный на Java, - был уже очень большого размера. Стало понятно, что во многом Java нас не устраивает. Было несколько альтернативных языков программирования, но оказалось, что ни один из них не соответствует тем требованиям, которые мы выдвигаем к языку, на который хотели бы перейти.

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

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

Почему Kotlin называется именно так?

В тот момент, когда придумывалось название, на JVM (Java Virtual Machine - прим. «Бумаги» ) существовали еще языки, названные в честь островов: Java, Ceylon. И мы подумали: какой у нас есть остров рядом? Котлин. И это название прижилось. Тут нет какой-то традиции или правила, но так случилось, какой-то более глубокой мысли за этим нет.

В чем особенности языка?

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

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

Чем же Kotlin так хорош? В первую очередь разработчики любят Kotlin за его краткость и выразительность. Это свойственно всем новым языкам. Раньше людей это не очень смущало, потом размер программ стал больше - люди поняли, что пишут очень много совершенно бессмысленных кусков кода только потому, что от них это требует синтаксис языка программирования.

Вторая причина в том, что он полностью совместим с Java и позволяет постепенно мигрировать с Java-приложения на Kotlin-приложение. Например, приложение Basecamp в течение полугода полностью мигрировало с Java на Kotlin.

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

Как Kotlin заметили разработчики приложений?

В JetBrains мы не занимаемся Android-разработкой и изначально никто не думал, что Kotlin будет языком, который так удачно подойдет для целей Android-разработчиков. Но в какой-то момент оказалось, что Android застрял на Java 6 и очень многие новые фичи Java на Android недоступны. Тогда прогрессивные разработчики обратили внимание на Kotlin.

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

Год назад у нас произошло довольно большое событие: система сборки Gradle, с помощью которой собираются все приложения для Android, объявила о переходе на Kotlin.

В каком-то смысле история с Kotlin на Android - совершенно сказочная и хрестоматийная: мы просто делали язык программирования, и он очень нравился разработчикам. Это история про движение снизу вверх, а не наоборот. Разработчики давно просили Google поддержать Kotlin. И Google к ним прислушался.

С анонсом от Google формально для нас ничего не меняется: мы продолжаем разрабатывать язык и нацелены на разные платформы. Естественно, мы предвкушаем особое внимание к языку со стороны Android-разработчиков: оно будет, в частности, выражено в сообщениях об ошибках, в запросах на поддержку той или иной функциональности, и, естественно, мы будем всё это обрабатывать. Но в целом, конечно, продолжим двигаться по намеченному пути.

Кто и зачем использует язык Kotlin?

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

Сейчас язык Kotlin в своих приложениях используют такие российские компании, как Avito и «Рокет Банк». За прошлый год Kotlin опробовали 160 тысяч программистов. До сегодняшнего дня Kotlin показывал экспоненциальный рост по числу программистов, и, думаю, объявление Google поможет нам продолжить этот рост.

Fun main(args: Array) { val numbers = arrayListOf(15, -5, 11, -39) val nonNegativeNumbers = numbers.filter { it >= 0 } println(nonNegativeNumbers) } // Вывод: 15, 11

Функции высшего порядка - это функции, которые принимают другие функции в качестве аргументов и возвращают функции. Рассмотрим следующий пример:

Fun alphaNum(func: () -> Unit) {}

В нём func - это имя аргумента, а () -> Unit - это тип функции. Мы говорим, что func будет функцией, не принимающей аргументов и ничего не возвращающей.

Лямбда-выражения, или анонимные функции - это функции, которые не объявляются, а передаются в виде выражений. Вот пример:

Val sum: (Int, Int) -> Int = { x, y -> x + y }

Мы объявляем переменную sum , которая берёт два числа, складывает их и принимает значение суммы, приведённое к целому. Для вызова достаточно простого sum(2,2) .

Сравнение скорости Java и Kotlin

Первая сборка Kotlin-кода занимает примерно на 15–20% больше времени, чем аналогичный процесс на Java. Однако инкрементная сборка Kotlin даже немного быстрее, чем у Java. Таким образом, языки примерно равны по скорости компиляции.

Будущее Kotlin

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

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

Вам потребуются следующие библиотеки:

  • Retrofit 2.0;
  • RxJava;
  • Picasso;
  • RecyclerView;
  • Расширения Kotlin для Android;
  • Dagger 2.

Все исходники доступны на GitHub . Серия состоит из следующих частей.

Эта статья рассказывает о языке программирования Kotlin. Вы узнаете о причинах появления проекта, возможностях языка и посмотрите несколько примеров. Статья написана в первую очередь в расчете на то, что читающий знаком с языком программирования java, однако, знающие другой язык, тоже смогут получить представление о предмете. Статья носит поверхностный характер и не затрагивает вопросы связанные с компиляцией в javascript. На официальном сайте проекта вы можете найти полную документацию, я же постараюсь рассказать о языке вкратце.

О проекте

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

О языке

Язык статически типизирован. Но по сравнению с java, компилятор Kotlin добавляет в тип информацию о возможности ссылки содержать null, что ужесточает проверку типов и делает выполнение более безопасным:

Fun foo(text:String) { println(text.toLowerCase()) // NPE? Нет! } val str:String? = null // String? -- тип допускающий null-ы foo(str) // <- компилятор не пропустит такой вызов -- // тип str должен быть String, чтобы // передать его в foo

Несмотря на то, что такой подход может избавить программиста от ряда проблем связанных с NPE, для java-программиста поначалу это кажется излишним - приходится делать лишние проверки или преобразования. Но через некоторое время программирования на kotlin, возвращаясь на java, чувствуешь, что тебе не хватает этой информации о типе, задумываешься об использовании аннотаций Nullable/NotNull. С этим связаны и вопросы обратной совместимости с java - этой информации в байткоде java нет, но насколько мне известно, этот вопрос еще в процессе решения, а пока все приходящие из java типы - nullable.

Кстати, об обратной совместимости: Kotlin компилируется в байткод JVM (создатели языка тратят много сил на поддержку совместимости), что позволяет использовать его в одном проекте с java, а возможность взаимно использовать классы java и Kotlin делают совсем минимальным порог внедрения Kotlin в большой уже существующий java-проект. В этой связи важна возможность использовать множественные java-наработки, создавая проект целиком на kotlin. Например, мне почти не составило труда сделать небольшой проект на базе spring-webmvc.

Посмотрим фрагмент контроллера:

Path(array("/notes/")) controller class NotesController { private autowired val notesService: NotesService? = null path(array("all")) fun all() = render("notes/notes") { addObject("notes", notesService!!.all) } //... }

Видны особенности использования аннотаций в Kotlin: выглядит местами не так аккуратно, как в java (касается это частных случаев, например, массива из одного элемента), зато аннотации могут быть использованы в качестве «самодельных» ключевых слов как autowired или controller (если задать алиас типу при импорте), а по возможностям аннотации приближаются к настоящим классам.

Надо заметить, что Spring не смог обернуть kotlin-классы для управления транзакциями - надеюсь, в будущем это будет возможно.

В языке есть поддержка first-class functions. Это значит, что функция - это встроенный в язык тип для которого есть специальный синтаксис. Функции можно создавать по месту, передавать в параметры другим функциям, хранить на них ссылки:

Fun doSomething(thing:()->Unit) { // объявляем параметр типа функция // ()->Unit ничего не принимает и // ничего важного не возвращает thing() // вызываем } doSomething() { // а здесь на лету создаем функцию типа // ()->Unit и передаем её в функцию doShomething // если функция -- последний параметр, можно // вынести её за скобки вызова println("Hello world") }

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

Fun List.filter(condition:(T)->Boolean):List { val result = list() for(item in this) { if(condition(item)) result.add(item) } return result } val someList = list(1, 2, 3, 4).filter { it > 2 } // someList==

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

Чтобы проиллюстрировать тему классов и краткости, посмотрим на следующий код:

// создание bean-классов становится // немногословным, поля можно объявить // прямо в объявлении конструктора class TimeLord(val name:String) // класс может вообще не иметь тела class TARDIS(val owner:TimeLord) fun main(args:Array) { val doctor = TimeLord("Doctor") val tardis = TARDIS(doctor) println(tardis.owner.name) }

В нескольких строках мы смогли объявить два класса, создать два объекта и вывести имя владельца ТАРДИСа! Можно заметить, что класс объявляется с параметрами своего единственно возможного конструктора, которые одновременно являются и объявлением его свойств. Предельно коротко, но при этом информативно. Наверняка найдутся те, кто осудит невозможность объявить больше одного конструктора, но мне кажется, что в этом есть свой прагматизм - ведь несколько конструкторов в java или позволяют объявить параметры по-умолчанию, что Kotlin поддерживает на уровне языка, или преобразовать один тип к другому, с которым и будет это класс работать, а это уже можно спокойно отдать на откуп фабричному методу. Обратите своё внимание на объявление «переменных» и полей. Kotlin заставляет нас сделать выбор: val или var . Где val - объявляет неизменяемую final -ссылку, а var - переменную, чем помогает избежать повсеместного использования изменяемых ссылок.

Пример

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

Так я бы хотел, чтобы выглядело использование:

Fun main(args: Array) { // создаем небольшое дерево val tree= tree("root") { node("1-1") { node("2-1") node("2-2") } node("1-2") { node("2-3") } } // обходим его и выводим значения в консоль tree.traverse { println(it) } }

Теперь попробуем это реализовать. Создадим класс узла дерева:

/** * @param value данные узла */ class Node(val value:T) { // дети узла private val children:List> = arrayList() /** * Метод, который создает и добавляет ребенка узлу * @param value значение для нового узла * @param init функция инициализации нового узла, необязательный * параметр */ fun node(value:T, init:Node.()->Unit = {}):Node { val node = Node(value) node.init() children.add(node) return node } /** * Метод рекурсивно обходит все дочерние узлы начиная с самого * узла, о каждом узле оповещается обработчик * @param handler функция обработчик для значения каждого узла */ fun traverse(handler:(T)->Unit) { handler(value) children.forEach { child -> child.traverse(handler) } } }

Теперь добавим функцию для создания вершины дерева:

/** * Создает вершину дерева со значением value и инициализирует * её детей методом init. */ fun tree(value:T, init:Node.()->Unit): Node { val node = Node(value) // вызываем метод init, переданный в // параметр, на объекте ноды node.init() return node }

В двух местах кода была использована конструкция вида Node.()->Unit, её смысл в том, что на вход ожидается тип-функция, которая будет выполняться как метод объекта типа Node. Из тела этой функции есть доступ к другим методам этого объекта, таким как метод Node.node(), что позволяет сделать инициализацию дерева, подобную описанной в примере.

Вместо заключения

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

Если вас заинтересовал язык, всю информацию о языке можно найти на официальном сайте проекта, ихсодники на github-е, а найденные ошибки постить в Issue Tracker. Проблем пока много, но разработчики языка активно с ними борются. Сейчас команда работает над пока еще не очень стабильной версией milestone 3, после стабилизации, насколько мне известно, планируется использовать язык внутри компании JetBrains, после чего уже планируется выход первого релиза.

Теги:

  • kotlin
  • java
  • jetbrains
Добавить метки

18.05.2017, Чт, 14:39, Мск , Текст: Александр Корнев

Разработчики из Google сделали Kotlin языком первого класса для ОС Android, отметив при этом, что он пока не станет заменой основному «первоклассному» языку Java.

Kotlin как язык первого класса для Android

В четверг на ежегодной конференции Google I/O разработчики сообщили о том, что созданный российскими программистами язык Kotlin станет «языком первого класса» для написания приложений для Android. Как пишет TechCrunch, в Google также рассказали об организации совместно с создателями языка - компанией JetBrains - специального фонда для развития Kotlin.

В Google подчеркнули, что Kotlin будет дополнительным языком и ни в коем случае не заменит Java и C++ (на сегодняшний день языком первого класса для Android является именно Java). Планируется, что инструменты Kotlin, основанные на JetBrains IDE, будут по стандарту включены в Android Studio 3.0 - официальный инструмент разработки для ОС Android.

«Поскольку Kotlin полностью поддерживается Java, вы и раньше могли писать приложения на нем, однако теперь это будет поддержано авторитетом и репутацией Google», - отмечают журналисты.

Google переводит Android на язык Kotlin, созданный в России

При этом Google не становится владельцем Kotlin. Права по-прежнему будут принадлежать JetBrains. Язык продолжит работать с другими платформами, к примеру, в качестве нативного кода для iOS и Mac или для компилирования кода JavaScript для веб-приложений.

В конкурентной борьбе со Swift

Kotlin - статически типизированный язык программирования, работающий поверх JVM, компилирующийся в JavaScript. Язык разрабатывается с 2010 г., его исходный код был открыт в 2012 г. Свое название Kotlin получил в честь острова Котлин в Финском заливе, на котором расположен Кронштадт.

Напомним, весной 2016 г. в Google о перспективе разработки приложений под Androidна Swift - языке программирования для iPhone и iPad. И тогда же в качестве возможного альтернативного языка был назван Kotlin. Сперва Swift был внутренним проектом Apple, но позже его исходный код был открыт разработчикам.

Сообщалось, что возможной причиной поиска замены для Java могли стать проблемы юридического плана с корпорацией Oracle, которая настаивает на том, что Google нарушила ее авторское право и патенты. Эксперты отмечали, что для адаптации Swift к Android потребовалась бы разработка новой среды исполнения для мобильной ОС, адаптация стандартной библиотеки, обеспечение поддержки языка в интерфейсах программирования (API) и инструментарии разработчика и т. д.

От Kotlin язык Swift отличается, по мнению экспертов, более высокой производительностью. Среди плюсов Kotlin отмечается уже упомянутая полная совместимость с JavaScript.

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

07.11.2011 Андрей Бреслав

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

История разработки «альтернативных» языков на платформе Java насчитывает более десятилетия, однако распространение такие языки получили лишь относительно недавно. На волне популярности динамически типизированных языков поднялись JRuby, Groovy и Clojure, а среди статически типизированных языков следует упомянуть Scala, Fantom и Gosu. Сам язык Java тоже не стоит на месте , но его развитие осложнено как необходимостью сохранения обратной совместимости, так и непростой судьбой компании Sun Microsystems, поглощенной корпорацией Oracle.

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

Разработка проекта Kotlin началась летом 2010 года, в июле 2011-го проект был официально анонсирован и на сайте размещено описание языка. Выпуск публичной бета-версии компилятора запланирован на начало 2012 года.

Создавая язык, мы руководствовались рядом требований, которые кажутся наиболее важными для такого проекта.

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

Статические гарантии корректности. Во время компиляции кода на статически типизированном языке происходит множество проверок, призванных гарантировать, что те или иные ошибки не произойдут во время выполнения. Например, компилятор Java гарантирует, что объекты, на которых вызываются те или иные методы, «умеют» их выполнять, то есть что в соответствующих классах эти методы реализованы . К сожалению, кроме этого очень важного свойства, Java почти ничего не гарантирует. Это означает, что успешно скомпилированные программы завершаются с ошибками времени выполнения (вызывают исключительные ситуации). Ярким примером является разыменование нулевой ссылки, при котором во время выполнения вызывается исключение типа NullPointerException. Важным требованием к новому языку является усиление статических гарантий. Это позволит обнаруживать больше ошибок на этапе компиляции и, таким образом, сокращать затраты на тестирование.

Скорость компиляции. Статические проверки упрощают программирование, но замедляют компиляцию, и здесь необходимо добиться определенного баланса. Опыт создания языков с мощной системой типов (наиболее ярким примером является Scala) показывает, что такой баланс найти непросто: компиляция зачастую становится неприемлемо долгой. Вообще, такая характеристика языка, как время компиляции проекта, может показаться второстепенной, однако в условиях промышленной разработки, когда объемы компилируемого кода очень велики, оказывается, что этот фактор весьма важен - ведь пока код компилируется, программист зачастую не может продолжать работу. В частности, быстрая компиляция является одним из важных преимуществ Java по сравнению с C++, и Kotlin должен это преимущество сохранить.

Лаконичность. Известно, что программисты зачастую тратят больше времени на чтение кода, чем на его написание, поэтому важно, чтобы конструкции, доступные в языке программирования, позволяли писать программы кратко и понятно. Java считается многословным языком (ceremony language - «церемонный язык»), и задача Kotlin - улучшить ситуацию в этом смысле. К сожалению, строгие методы оценивания языков с точки зрения их лаконичности развиты довольно слабо, но есть косвенные критерии; один из них - возможность создания библиотек, работа с которыми близка к использованию предметно-ориентированных языков (Domain-Specific Language, DSL). Для создания таких библиотек необходима определенная гибкость синтаксиса в совокупности с конструкциями высших порядков ; наиболее распространены функции высших порядков, то есть функции, принимающие другие функции в качестве параметров.

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

Инструментальная поддержка. Современные программисты активно используют различные автоматизированные инструменты, центральное место среди которых занимают интегрированные среды разработки (Integrated Development Environment, IDE). Десятилетний опыт, накопленный в компании JetBrains, показывает, что определенные свойства языка могут существенно затруднять инструментальную поддержку. При разработке Kotlin мы учитываем этот факт и создаем IDE одновременно с компилятором.

Основные элементы языка

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

Объявления в Kotlin объединяются в пространства имен (namespace), и функция может быть объявлена непосредственно внутри пространства имен:

namespace example { fun max(a: Int, b: Int) : Int { if (a returna return b } }

Объявление функции предваряется ключевым словом fun , а типы параметров указываются после двоеточия, следующего за именем параметра. Аналогично обозначается и тип возвращаемого значения функции. Такой синтаксис следует традициям языков «функционального мира», например ML и Scala. Он позволяет легко опускать типовые аннотации, если тип может быть выведен компилятором из контекста автоматически.

Переменные в Kotlin, как и в Scala, объявляются с помощью ключевых слов val (неизменяемые переменные) и var (изменяемые):

var sinSum: Double = 0.0 for (x in xs ) { val y = sin(x) sinSum += y }

В данном примере тип неизменяемой переменной y опущен, поскольку компилятор может его вывести автоматически по значению правой части определения переменной. Тип изменяемой переменной sinSum указан явно лишь для того, чтобы продемонстрировать соответствующий синтаксис; компилятору достаточно информации, чтобы вывести тип и в этом случае.

Классы. Основным инструментом декомпозиции в Kotlin, как и в других объектно-ориентированных языках, являются классы. При объявлении класса список параметров конструктора указывается непосредственно в заголовке:

class IntPair(x: Int, y: Int) {...}

Экземпляры класса создаются прямым вызовом конструктора; ключевого слова new в Kotlin нет:

val xy = IntPair(x, y)

В заголовке класса также указывается список типовых параметров (generic) в угловых скобках и список супертипов , то есть типов, от которых данный класс наследуется, отделяемый двоеточием:

class MyList (length: Int): List , Serializable {...}

Трейты. Класс может наследоваться от одного класса или от нескольких трейтов (trait – дословно «характерная черта», особенность). Трейты похожи на классы тем, что они тоже определяют типы, тоже могут иметь функции-члены и наследоваться от других трейтов. Основное отличие состоит в том, что трейты не имеют конструкторов и, как следствие, не могут иметь состояния (полей). Можно сказать, что трейты - это знакомые всем интерфейсы из языка Java, только функции в них могут иметь реализацию. Ограничения, накладываемые на трейты, позволяют избежать трудностей, связанных с множественным наследованием классов.

Внешние функции. Еще один механизм «расширения» типов в Kotlin - это внешние функции (extension function - «функция-расширение»). Такие функции могут быть объявлены вне какого-либо класса и при этом вызываться так, как будто они были объявлены внутри. Вот пример объявления внешней функции для типа Int:

fun Int.abs() : Int { if (this return -this else return this }

Тип, расширяемый данной функцией, указывается перед ее именем и отделяется точкой. Это соответствует объявлению «неявного» параметра, который внутри функции обозначается ключевым словом this . Например, в нашем примере функция abs() расширяет тип Int, и неявный параметр this является целым числом. Такую функцию можно вызывать с помощью операции «точка», как и функции - члены класса:

val x = (-1).abs()

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

Внешние функции связываются статически , то есть не являются виртуальными (virtual).

Управляющие конструкции. When

Kotlin поддерживает традиционные для императивных языков управляющие конструкции if , for и while , на которых мы не будем останавливаться подробно, а также конструкцию when - операцию ветвления, которую можно рассматривать как расширенную версию традиционного оператора switch :

when (x) { 1 => print(“One”) 2, 3 => print(“Two or three”) else => print(“Unknown number”) }

Слева от знака «=>» указывается выражение или список выражений, разделенных запятыми, называемый условием . Если аргумент конструкции when (в нашем примере - переменная x) равен хотя бы одному из этих выражений, выполняется тело данного условия, то есть выражение или блок, указанный справа от знака «=>». Условия проверяются последовательно, сверху вниз. Если ни одно из условий не выполнено, выполняется код, указанный после слова else . В отличие от switch , при использовании when условия не являются метками, поэтому не требуется заканчивать тело условия словом break .

Кроме простого сравнения на равенство, when позволяет проверять аргумент на принадлежность коллекции, с помощью операции in :

when (x) { in set => print(“in set”) in 1..10 => print(“1..10”) else => print(“Default branch”) }

После ключевого слова in указывается выражение, имеющее любой тип, поддерживающий метод contains(). В частности, может быть указана коллекция (как в первом условии в данном примере) или промежуток (как во втором). Промежутки могут быть образованы как целыми, так и дробными числами, причем крайние значения (в данном примере 1 и 10) включаются.

Еще один вид условий отмечается ключевым словом is и позволяет проверять тип аргумента . Например, проверить к какому из нескольких типов относится значение x, можно следующим образом:

when (x) { is String => println(“String”) is Int => println(“Int”) is Array => println(“Array of Double”) }

Система типов

Нулевые ссылки. Система типов языка Kotlin позволяет гарантировать отсутствие в программах некоторых видов ошибок, например разадресации нулевой ссылки. Типы в Kotlin делятся на содержащие null и не содержащие null . Типы, содержащие null , аннотируются знаком вопроса:

fun isEmpty(s: String?): Boolean {...}

Знак вопроса после имени типа (String) означает, что ссылка s указывает на объект класса String или имеет значение null . Результат функции isEmpty, в свою очередь, должен быть булевым значением и не может иметь значения null , поскольку соответствующий тип не проаннотирован знаком вопроса.

return s.length() == 0 // Ошибка: разыменование нулевой ссылки

Необходимо явно проверить, ссылается ли s на существующий объект:

If (s != null ) { return s.length() == 0 // s точно ссылается на существующий объект } else { return true } return (s == null ) || s.length() == 0//Оператор||обеспечивает проверку

Часто встречаются длинные цепочки вызовов, каждый из которых может вернуть null . В результате мы получаем несколько вложенных условий, проверяющих, что вернул каждый из вызовов в цепочке. Чтобы избежать загромождения кода, в Kotlin поддерживается оператор безопасного вызова , обозначающийся «?.»:

A?.getB()?.getC()?.getD()

Если a не равно null , выражение a?.getB() возвращает a.getB(), а в противном случае - null .

Автоматическое приведение типа. Мы приводили пример того, как компилятор учитывает информацию, содержащуюся в условиях, и разрешает разыменовывать уже проверенные ссылки. Аналогичный механизм автоматически вставляет операцию приведения типа , если ранее в программе проверялось соответствующее условие. Оператор проверки типа (аналог instanceof в Java) в Kotlin называется is :

val x: Object = ... if (x is String) { print(x.length()) }

В этом примере ссылка x проверяется на принадлежность к типу String, и, если проверка прошла успешно, на экран выводится длина строки. При вызове функции length() компилятор автоматически вставляет приведение x к типу String, поскольку оно безопасно в этом месте программы.

Автоматическое приведение типа работает для всех условных конструкций: if , when , while , ||, && и т. д.

Функции высших порядков

Пользователи функциональных языков программирования хорошо знакомы с функциями высших порядков : механизмом, позволяющим передавать функции в качестве аргументов другим функциям, записывать функции в переменные и т. д. Как уже говорилось, этот механизм значительно облегчает создание библиотек. В объектно-ориентированных языках функции высших порядков обычно эмулируются с помощью паттерна Strategy . Например, для того чтобы реализовать фильтрацию для коллекций произвольного типа, необходимо параметризовать функцию filter() объектом, который «умеет» отвечать на вопрос, нужно ли включить данный элемент в результирующую коллекцию. Этот объект является стратегией фильтрации . В функциональном языке создавать стратегию не нужно - можно просто передать функцию.

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

val predicate: fun (x: Int): Boolean =...

В данном примере переменная predicate имеет функциональный тип «fun (x: Int): Boolean», то есть хранимое ею значение является функцией, принимающей целочисленный параметр и возвращающей булевское значение. В частности, мы можем вызвать эту функцию:

val yesOrNo = predicate(1)

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

fun Collection .intFilter(predicate: fun (x: Int): Boolean): Collection {...}

А для произвольных коллекций можно ее обобщить с помощью типовых параметров (generic):

fun Collection .filter(predicate: fun (x: T) : Boolean) : Collection { val result = ArrayList () for (x in this ) if (predicate(x)) result.add(x) return result }

(Здесь приведена лишь наивная реализация функции filter(). Более реалистичная реализация требует ленивых вычислений, но эта тема выходит за рамки данной статьи.)

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

Ints.filter({x => x % 2 == 0})

В данном примере аргумент внешней функции filter() представляет собой функциональный литерал, то есть короткое объявление функции. Он обязательно заключается в фигурные скобки, до символа «=>» следуют объявления параметров, а после - тело функции, причем оператор return не требуется, поскольку результатом считается последнее выражение в теле литерала. Таким образом, из коллекции ints будут выбраны только четные числа , поскольку наш литерал возвращает true, только если остаток от деления x на 2 равен нулю.

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

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

Ints.filter({it % 2 == 0}) // Вызов аналогичен предыдущему примеру

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

Ints.filter {it % 2 == 0} // Вызов аналогичен двум предыдущим примерам

Такой синтаксис позволяет записывать преобразования коллекций в стиле, напоминающем LINQ :

Ints.select {it * it}. where {it % 2 == 0} // Среди квадратов элементов коллекции // выбрать четные

(Здесь функция where() делает то же, что и функция filter().)

Кроме того, данная конвенция делает вызовы функций больше похожими на привычные управляющие конструкции. Приведем еще один пример. Функция synchronized() принимает два параметра: объект синхронизации (монитор) и функцию. Во время выполнения сначала захватывается монитор, далее в блоке try..finally выполняется функция, а затем монитор освобождается:

fun synchronized (l: Lock, body: fun () : T) : T { l.lock() try { return body() } finally { l.unlock() } }

Для того чтобы вызвать фрагмент кода с синхронизацией, используя данную функцию, достаточно написать:

Synchronized (myLock) { // Код, который необходимо выполнить }

Данный пример показывает, как в Kotlin можно средствами языка выразить конструкцию, которая в Java является встроенной.

Предметно-ориентированные языки

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

val homeDir = "..." module("org.jetbrains.test") { // Объявление модуля depends(MavenRepo("[email protected]")) // Зависимость importNamespace("java.lang") // Импорт по умолчанию importNamespace("org.junit") // Импорт по умолчанию sourceRoot("$homeDir/src") // Путь к исходным файлам testSourceRoot("$homeDir/tests") // Путь к тестам }

Несмотря на то что перед нами обыкновенная программа на Kotlin, она выглядит как программа на специализированном языке, предназначенном для декларативного описания модулей. Такой подход к описанию структур данных весьма популярен в Groovy и других динамических языках, поскольку позволяет избежать громоздких и трудно читаемых дескрипторов, написанных на XML. В Groovy такой подход известен под названием Builders . Ярким примером его использования является библиотека Gradle .

По сравнению с Groovy и другими динамическими языками, важным отличием внутренних DSL в Kotlin является то, что при той же краткости синтаксиса, система типов статически гарантирует корректность программы.

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

// Абстрактный модуль abstract class Module(name: String) { val dependencies: List = ArrayList() } // Модуль, состоящий из классов, написанных на Kotlin class KotlinModule(name: String) : Module(name) { fun dependency(module: Module) { dependencies.add(module) } } // Модуль на основе репозитория Maven class MavenRepo(name: String) : Module(name) { ... }

Определим функцию module(), создающую новый модуль:

fun module(name: String, init: fun KotlinModule.() : Unit) : KotlinModule { val result = KotlinModule(name) result.init() return result }

Данная функция является функцией высшего порядка, поскольку параметр init сам является функцией, причем внешней функцией: об этом говорит тип KotlinModule, указанный перед (пустым) списком параметров в функциональном типе. Это означает, что функцию module() можно вызывать следующим образом:

Module(“org.jetbrains.test”) { // тело функции init }

Это похоже на пример сценария, который мы уже видели. Заметим, что внутри функционального литерала доступен неявный параметр this типа KotlinModule (поскольку этот литерал имеет тип «внешняя функция»), и мы можем его использовать:

Module(“org.jetbrains.test”) { this .dependency(MavenRepo("[email protected]")) this .dependency(anotherModule) // ... }

Осталось заметить, что this , как обычно, можно опустить, и мы получим в точности такой же синтаксис, как в примере сценария, приведенном в начале данного раздела:

Module(“org.jetbrains.test”) { dependency(MavenRepo("[email protected]")) dependency(anotherModule) // ... }

Аналогичным образом можно реализовать многие декларативные языки, включая языки разметки, такие как HTML. При этом вместо тегов будут использоваться вызовы функций высших порядков, и корректность использования таких «тегов», как и их атрибутов, будет гарантироваться системой типов:

Html { head { title {+"XML encoding with Kotlin"} } body { h1 {+"XML encoding with Kotlin"} p { +"This is some text. For more see the" a(href = "http://jetbrains.com/kotlin") {+"Kotlin home page"} } }

Мы рассмотрели несколько наиболее интересных особенностей языка Kotlin, но за рамками остались такие возможности языка, как обобщенные типы (generic), встраиваемые функции (inline function), представление типов во время выполнения (reified type), поддержка делегирования, переопределение операторов и др. Об этих возможностях, а также о ходе работы над проектом можно прочесть на странице проекта.

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

Литература

  1. Bloch J. Effective Java. Second Edition. - Prentice Hall, 2008.
  2. Гамма Э., Хелм Р., Джонсон Р., Влиссидес Дж. Приемы объектно-ориентированного проектирования. Паттерны проектирования. - СПб.: Питер, 2007.
  3. Троэлсен Э. Язык программирования С# 2008 и платформа. NET 3.5. - М.: Вильямс, 2010.

За исключением случаев несогласованной раздельной компиляции. - Прим. автора.

Операция is также позволяет проводить сопоставление с образцом (pattern matching) - Прим. автора.

Андрей Бреслав ([email protected]) - ведущий разработчик языка Kotlin, компания JetBrains.





Понравилась статья? Поделиться с друзьями: