Эта статья рассказывает о языке программирования 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 <T> List<T>.filter(condition:(T)->Boolean):List<T> {
val result = list<T>()
for(item in this) {
if(condition(item))
result.add(item)
}
return result
}
val someList = list(1, 2, 3, 4).filter { it > 2 } // someList==[3, 4]
Обратите внимание на то, что у переменных не указаны типы — компилятор Kotlin выводит их, если это возможно и не мешает понятности интерфейса. Вообще, язык сделан таким образом, чтобы максимально избавить человека за клавиатурой от набирания лишних знаков: короткий, но понятный синтаксис с минимум ключевых слов, отсутствие необходимости точек с запятой для разделения выражений, вывод типов, где это уместно, отсутствие ключевого слова new для создания класса — только необходимое.
Чтобы проиллюстрировать тему классов и краткости, посмотрим на следующий код:
// создание bean-классов становится
// немногословным, поля можно объявить
// прямо в объявлении конструктора
class TimeLord(val name:String)
// класс может вообще не иметь тела
class TARDIS(val owner:TimeLord)
fun main(args:Array<String>) {
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<String>) {
// создаем небольшое дерево
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<T>(val value:T) {
// дети узла
private val children:List<Node<T>> = arrayList()
/**
* Метод, который создает и добавляет ребенка узлу
* @param value значение для нового узла
* @param init функция инициализации нового узла, необязательный
* параметр
*/
fun node(value:T, init:Node<T>.()->Unit = {}):Node<T> {
val node = Node<T>(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 <T> tree(value:T, init:Node<T>.()->Unit): Node<T> {
val node = Node(value)
// вызываем метод init, переданный в
// параметр, на объекте ноды
node.init()
return node
}
В двух местах кода была использована конструкция вида Node<T>.()->Unit
, её смысл в том, что на вход ожидается тип-функция, которая будет выполняться как метод объекта типа Node<T>
. Из тела этой функции есть доступ к другим методам этого объекта, таким как метод Node<T>.node()
, что позволяет сделать инициализацию дерева, подобную описанной в примере.
Вместо заключения
За счет хорошей совместимости с java и возможности заменять старый код постепенно, в будущем Kotlin мог бы стать хорошей заменой java в больших проектах и удобным инструментом для создания небольших проектов с перспективой их развития. Простота языка и его гибкость дает разработчику больше возможностей для написания быстрого, но качественного кода.
Если вас заинтересовал язык, всю информацию о языке можно найти на официальном сайте проекта, ихсодники на github-е, а найденные ошибки постить в Issue Tracker. Проблем пока много, но разработчики языка активно с ними борются. Сейчас команда работает над пока еще не очень стабильной версией milestone 3, после стабилизации, насколько мне известно, планируется использовать язык внутри компании JetBrains, после чего уже планируется выход первого релиза.
Автор: fogone