Kotlin без магии

Kotlin без магии

Mike Gorunov

Обычно, когда открываешь для себя новый язык программирования, некоторые его конструкции выглядят, как магия. Например, for-each loop в Java:

for (String s : strings) {
    /* ... */
}

Какого типа должна быть strings? Во что разворачивается эта конструкция?

(для массива (допустим, T[]) получается подобие for (int i = 0; i < array.length; i++) { T t = array[i]; /* ... */ }, а для Iterable<T> — нечто вроде for (Iterator<T> itr = iterable.iterator; itr.hasNext(); ) { T t = itr.next(); /* ... */ })

Или, скажем, как работает try-with-resources ( try (InputStream is = new FileInputStream(file)) { /* ... */ })? Какой finally вызовется раньше — сгенерированный компилятором и закрывающий ресурсы или наш собственный? Что нужно сделать чтобы собственный класс работал с try-with-resources? (ну да, реализовать AutoCloseable)

Пример ещё проще и повседневнее.

String first = getStringSomewhere();
String second = "first: " + first;
String third = "first: " + first + "; hashCode: " + first.hashCode();

Как переопределён оператор сложения? Где будет создаваться StringBuilder, а где — нет?

Ещё пример. Код на Groovy.

def personDetails = [firstName:'John', lastName:'Doe', age:25]

Какой класс у personDetails? HashMap, TreeMap, LinkedHashMap, Collections$UnmodifiableMap?

Что там про Kotlin?

В Kotlin нет магических и тайных языковых конструкций.

Array<T>.forEach и Collection<T>.forEach — это extension functions, которые определены как for (element in this) action(element). При Ctrl-клике (Cmd-клике) на in можно увидеть, что этот оператор работает с функциями iterator, next и hasNext. Последние, в свою очередь, определены как operator fun, что и позволяет использовать их с языковыми конструкциями. То есть автор класса, написав operator fun, явно разрешает использовать эту функцию как конструкцию языка.

В выражении someFile.bufferedReader.use { /* тот же try-with-resources */ }, например, use — инлайновая (встраиваемая) extension-функция для Closeable. Можно посмотреть в её код и понять, когда там вызывается finally.

В коде val s = "a" + "b" можно Ctrl-кликнуть плюс и увидеть operator fun plus в классе kotlin.String.

В конструкции val personDetails = mapOf("firstName" to "John", "lastName" to "Doe", "age" to 25) можно заглянуть в реализацию mapOf (также есть mutableMapOf, hashMapOf, linkedMapOf) и в реализацию to (infix fun <a, b="">A.to(that: B): Pair <a, b="">= Pair(this, that)</a,></a,>).

Лично для меня при всём синтаксическом разнообразии и гибкости Kotlin код на нём остаётся очень понятным. Можно создавать свои функции, которые благодаря модификаторам operator, inline и infix выглядят как языковые конструкции, но всегда можно посмотреть в их реализацию, и нигде не будет чего-то вроде *тут магия компилятора*.