О языке Kotlin для Java-программистов

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

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



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

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

Similar posts

Comments 25

    +1
    Был на jeeconf в мае этого года, там Андрей Бреслав (возглавляет разработку языка) делал доклад по нему — рассказывает довольно интересно, но насколько я понимаю до хотя бы релиза ещё далековато. Кому интересно есть видео доклада jeeconf.com/materials/kotlin/
      0
      Скажите, а чем он лучше groovy 2.0 или той же scala?
        +2
        Groovy — динамическая типизация, Kotlin — статическая. Отсюда все вытекающие недостатки и преимущества (скорость работы приложений, обнаружение ошибок в compile-time и т.п.)

        Развернутое сравнение со Scala читайте здесь. В основе отличий лежат два принципа:

        1. Компиляция как минимум не медленнее, чем у Java
        2. Сделать язык по возможности более простым.

        Во избежание холивара, цитирую: «Если вас Scala полностью устраивает, возможно Kotlin вам и не нужен».
          0
          Groovy 2.0 хоть и поддерживает статическую типизацию всё же больше скриптовый язык, поэтому сравнивать я бы их не стал. Ситуацию со scala я попытался описать в разделе «о проекте». Если коротко, то скала сложный язык не только для программиста, но и для создание ide.
          0
          Начинал изучать Scala для общего развития, но в какой-то момент застопорился из-за того, что разработка даже «одноразовых скриптов» явно подразумевает знание сановских оракловских библиотек для Java, а в книгах по Scala они не описываются. С Kotlin такая же проблема?
            0
            С Котлином такой проблемы нет. Но надо признать, что и со scala я их не имел.
              0
              С Котлином такой проблемы нет, если вы имеете ввиду оракловые библиотеки. Знания же стандартной библиотеки java для программирования на котлине необходимы.
                0
                Под сановскими/оракловскими библиотеками я имел в виду именно стандартные Java, почему-то синонимичны для меня эти понятия.
                  0
                  Если язык сделан для Java платформы, то какой бы язык не был, стандартную библиотеку придётся таки изучить
                0
                Смотря что считать «одноразовыми скриптами».
                Например в scala есть удобное чтение из файла (scala.io.Source), но вот для записи в файл приходится использовать уже java.io.
                Согласитесь, действие весьма базовое.

                Есть, конечно, подвижки и в этом направлении, но они далеки от завершения.

                Та же работа с сокетами требует погружения в мир java.

                Если Вы скажете, что это не проблема, то я соглашусь. Но факт остается фактом: для работы со скалой требуются java-библиотеки.
                  0
                  Комментарием выше уточнил. Конечно, для программирования на Котлин необходимо знание стандартной библиотеки java, потому что полноценной своей у него нет. Но лично я не вижу в этом проблемы — стандартная библиоткеа у java неплохая, главное проверенная временем и тысячами проектов.
                    0
                    Дело не в том, что плохая или нет, а в том, что слабо представляю её изучение без изучения Java. Грубая аналогия: если бы для изучения Си нужно было бы знать Ассемблер — не помешает, а в некоторых случаях необходимо, но в общем — будет оверхидом.
                      +1
                      Насколько я понимаю, котлин позиционируется именно как язык на замену java. Как язык, который можно было бы использовать вместе с java, постепенно заменять код java на код котлина.
                        +1
                        Не уверен по поводу Kotlin — все таки преподносится как простой язык, а вот scala — это язык скорее не для тех, кто не хочет изучать java, а для тех, кому она уже неудобна.

                        Можно считать стандартную библиотеку java частью стандартных библиотек котлина и скалы. В таком контексте изучение котлина или скалы предполагает изучение стандартной билиотеки java.
                          0
                          Что можно, а скорее нужно считать, я прекрасно понимаю. Просто как-то привык, что изучая новый язык я изучаю и его стандартную библиотеку по тем же источникам. То есть претензии, если можно так выразиться, не к самим языкам, а к учебным материалам. Просто нет описания стандартной библиотеки, в лучшем случае описание особенностей и отличий её применения по сравнению с Java. Видимо действительно языки не предназначены для использования в качестве «первого языка» (JVM-based).
                  +2
                  Ответ очевиден: с Kotlin эта проблема гораздо хуже.
                  Вокруг scala выросло довольно много как собственных инструментов (весь Typesafe stack, заумная, но удобная библиотека scalaz, входящий в стандартную библиотеку парсер), так и множество оберток над java-библиотеками.
                  Вокруг Kotlin ни чего подобного пока нет — молод еще.

                  С другой стороны есть и пара плюсов.
                  Реальный: синтаксический сахар с nullable работает уже с java-библиотеками. Плюс небольшой (в scala очень просто обернуть результат в Option, а котлин пользуется редко используемым атрибутом).
                  Обещаный: предполагается разработка внутри IDEA на Kotlin с выкладыванием разработанных для своих нужд библиотек в паблик. но чем они круче того же Typesafe я не знаю.
                  0
                  Поправка с учетом высказывания fogone:
                  «Если предположить. что в scala есть эта проблема, то» и дальше уже остальная часть моего комментария.
                    +1
                    init:Node<T>.()->Unit – что в Scala, что здесь — криптографический синтаксис.
                      0
                      Да нет, у Kotlin-а, в отличие от Scala, достаточно простой синтаксис. Другое дело, что он кое в чём отличается от java-вского — ну так иногда неплохо вылезать из уютного мирка java и смотреть, что творится вокруг.
                      0
                      В заголовке написано для Java разработчиков, но на самом деле котлин пригоден и для web-разработки. В JetBrains уже сейчас на нем пишутся расширения для Firefox/Chrome в плагинах JS Debugger/LiveEdit. Иными словами — можно забыть о кошмаре JS и писать на котлине.
                        0
                        Java тоже пригодна для web-разработки. Согласен, котлин мог бы сильно упростить жизнь разработчикам клиентского кода. Во введении написал, что вопрос компиляции в javascript намеренно опущен, так как требует отдельной статьи.
                        0
                        Когда будет плагин к Eclipse плюс допилят компиляцию в JS, пересяду на Kotlin. Сейчас юзаю GWT+XTend.
                          0
                          Помогите с переводом документации по Kotlin. www.kotlin.su
                            –1
                            Сделать новый язык совместимый с другим, для чего? Где логика? Если бы они вообще новый язык сделали, то понятно. Под ява столько библиотек! Совмещать котлин и яву? И вот теперь начинают воду мутить. Лучше бы они сделали шуструю IDE, чем изобретали велосипед! А то IDE просто тормоз (в сравнении MSVS).
                              +1
                              Надо было этот коммент написать в 2022 на юбилей поста.

                            Only users with full accounts can post comments. Log in, please.