На днях JetBrains после пятилетней работы выпустила первый релиз языка Kotlin. Давайте посмотрим, что же это за язык, попробуем разобраться зачем и для кого он, какие имеет функциональные особенности. Скорее всего в статью затесались и личные впечатления от языка, но я старался, чтобы они не влияли на изложение полезной информации. Если вы еще ничего или почти ничего не знаете о Kotlin, то я завидую вам, ибо по моему ощущению почитать про инструмент, который ты долго ждал, сродни распаковке новогоднего подарка. Впрочем судите сами.
Что такое Котлин
Котлин — это небольшой остров в Финском заливе недалеко от Санкт-Петербурга. Видимо, тем самым создатели дают отсылку к тому, что новый язык, как остров Котлин — младший русский брат далекого острова Ява.
Для кого этот язык
Действительно, новый язык компилируется в JVM байт-код (есть еще и компиляция в JavaScript, но так как релиз компилятора именно в JVM, эту тему придется опять отложить). А это значит, что он может заинтересовать всех, кто имеет дело с Java-машиной и в целом с языками со сборщиком мусора (а с выходом релиза компиляции в JavaScript покрытие и возможности будут еще шире).
Простой и совместимый
Две главных особенности котлина, на мой взгляд, это его простота и полная совместимость с Java. Котлин создавался компанией, которая делает очень много продуктов на Java и которая хорошо разбирается в современных инструментах разработки. Запрос на новый язык витает в воздухе давно, но сделать такой язык, который бы позволил взять (огромную) готовую кодовую базу Java, обычных Java-разработчиков, дать им новый инструмент и бесшовно (но более эффективно) продолжать разработку — такого инструмента до появления котлина не существовало. Создатели нового языка, на мой взгляд, очень хорошо почувствовали потребности бизнеса и разработчиков: бизнесу дали возможность увеличить эффективность разработчиков, а разработчикам дать современный инструмент для разработки. И когда я говорю о «современном инструменте», я, конечно, имею ввиду не только компилятор, но и поддержку в IDE, без которой лично моя деятельность разработчика мне видится совсем немыслимой.
В итоге: простота позволяет использовать язык почти любому Java-разработчику, который готов потратить полчаса на то, чтобы посмотреть туториал или спецификацию языка, обратная совместимость же позволяет использовать язык в уже существующем проекте.
Production-ready
Конечно, в первую очередь, запрос на этот язык был у самой JetBrains, отсюда и некоторое понимание, каким он должен быть. Более того, JetBrains же внутри компании его и тестировала: к моменту выхода этого релиза у компании уже есть как минимум один крупный продукт сделанный чисто на котлине (да, я понимаю, что платформа по прежнему написана на Java). Отсюда можно предположить, что заявления о готовности языка к продакшену не голословны. А из своего же опыта использования котлина с 2012 года могу сказать, что из тех проблем, с которыми я сталкивался в дорелизные времена, до релиза ни одна не дожила. Есть еще небольшие проблемы с плагином для IDEA, но сам компилятор работает как часы.
Совместим с Java 1.6
Это очень важный момент, ведь именно эта версия Java используется во всех современных версиях Android, и, не смотря на запланированный переход на OpenJDK, восьмая версия попадет в руки разработчикам под мобильные устройства не так скоро как хотелось бы. Конечно, есть всякие ретролямбды и другие ухищрения, но котлин — это не только лямбды для андроид, но и современный язык, позволяющий сделать разработку под андроид проще и приятней без особых затрат. А увеличение размера apk совсем не значительно по нынешним временам: 823KB (для версии 1.0.0)
Особенности
Полный список возможностей, конечно, лучше искать в документации, я же постараюсь отразить наиболее важные на мой взгляд моменты в общих словах:
Null safety
Почему-то исторически так сложилось, что эта особенность котлина вспоминается первой. И хотя она безусловно важна, на мой взгляд не является важнейшей. Здесь язык позволяет, определяя переменные, поля, константы и тд, указать, может ли в них храниться ссылка на null. Поднимает на новый уровень идею аннотаций вроде @Nullable и NotNull, позволяет умно приводить к не-nullable типу после проверки её на null. Надо отметить, что бывают случаи, когда эта особенность расходится с моим закостенелым разработкой на Java представлением о том как должны быть сделаны некоторые вещи, но после некоторого раздумья хорошее решение всегда находится.
/* вопросом помечаем, что может прийти null */
fun someFunction(someNullableParam:SomeType?) {
if(someNullableParam != null) {
// smart cast. Компилятор видит, что передаваемое
// значение не null и разрешает его передать в функцию
anotherFunction(someNullableParam)
}
}
/* здесь же уже null не пройдет, в попытке передать
* null или nullable значение компилятор выдаст ошибку */
fun anotherFunction(someParam:SomeType) {
// делаем что-то без опаски, что переданное значение null
}
Выведение типов
Да, котлин почти везде где возможно, умеет вывести тип, однако тип всё же придется определить для публичных методов и свойств, что очень разумно (мне подсказывают, что это неправда — с какой-то версии это убрали):
// Kotlin в курсе, что здесь List<Char>
val result = sequenceOf(10).map { it.toString() }.flatMap { it.toCharArray().asSequence() }.toList()
Extension methods
Возможность, которой мне остро не хватает в Java для увеличения гибкости языка и решений. Заключается в возможности определить метод для типа отдельно от его (типа) объявления. Такая функция, конечно, не будет виртуальной и никак не меняет класса, которому мы добавляем метод, однако позволяет добавить как утилитарную функциональность для уже существующего кода, так и разгрузить интерфейс от этих же утилитарных методов.
interface Vector2 {
val x:Float // это не поле, а ридонли свойство (property)
val y:Float // в Java были бы методы getX() и getY()
}
/* Extension property. Без поля в классе, просто getLength() */
val Vector2.length:Float
get() = (x * x + y * y).sqrt() // притворимся, что такая extension-функция для Float уже существует
/* переопределяем оператор + */
operator fun Vector2.plus(other:Vector2):Vector2 = createVector(x+this.x, y+this.y) // какой-то способ создать новый вектор
/* без тела, после знака = пишем одно выражение */
fun Vector2.dot(x: Float, y: Float): Float = x * this.x + y * this.y
/* Помечая функцию с одним параметром как infix,
* мы позволяем вызывать её через пробел: v1 dot v2 */
infix fun Vector2.dot(vec2: Vector2): Float = dot(vec2.x, vec2.y)
fun usage(vec1:Vector2, vec2:Vector2) {
val dotProduct = vec1 dot vec2
val sum = vec1 + vec2 // на выходе новый вектор
val length = sum.length // обращаемся просто как к свойству
}
Лямбды
Конечно, как любой современный язык с претензией на возможности функцинального программирования, у котлина функция — это сущность первого класса, если переводить дословно. Т.е. функции можно не только объявлять прямо в пакете (из джавы они видны всё равно в классах — по имени файла), но и передавать в качестве параметров, возвращать из других функций и тд. И сейчас, конечно, никого этим не удивишь, но например в сравнении с Java, где синтаксически функций как таковых нет (а только функциональные интерфейсы), в котлине полноценный синтаксис для объявления функции:
/* передаем в одну функцию другую -- принимает в параметр Int
* и Int же возвращает. Возвращаем её же, только с фиксированным
* значением в качестве параметра */
fun passTen(func: (Int)->Int ): ()->Int {
return { func(10) }
}
Extension-лямбды
Наряду с extension-методами, это еще одна моя любимая фича. Позволяет определить лямбду, которая будет еще и extension-методом. Звучит не очень, да. Посмотрим на примере:
class World(val name:String = "world")
val printName:World.()->Unit = {
// интерполяцией в синтаксисе языка сейчас уже
// никого не привлечь на темную сторону
println("Hello $name")
}
val world = World()
// вызываем нашу функцию как будто это метод нашего класса!
world.printName()
Эта возможность особенно интересно смотрится в билдерах, посмотреть на которые я предлагаю вам самостоятельно — на случай, если вам интересно, как получаются вот такие конструкции:
html {
head {
title {+"XML encoding with Kotlin"}
}
body {
h1 {+"XML encoding with Kotlin"}
a(href = "http://kotlinlang.org") {+"Kotlin"}
}
}
Inline-фукнции
Помечая функцию как inline мы просим компилятор поместить её по месту использования. Чаще всего такими вещами занимается рантайм, но есть кейзы, когда мы точно знаем, что фукнция это просто шорткат для какого-то действия — особенно эффективно это работает с передаваемыми лямбдами:
/* передаваемой лямбдой block сейчас уже никого не удивишь.
* Главно, что лишних затрат на вызов этой функции не будет вообще,
* иногда мне кажется, что это что-то вроде макросов */
inline fun lock(lock:Lock, block:()->Unit) {
lock.lock()
try {
block()
} finally {
lock.unlock()
}
}
fun usage() {
lock(Lock()) {
// делаем что-то внутри блокировки
}
}
Конечно, на такие функции накладывается серия ограничений, подробнее см. документацию.
Делегирование
В котлине есть два типа делегирования. Первый, который позволяет делегировать все методы реализуемого интерфейса к какому-то инстансу этого типа:
interface Connection {
fun connect()
}
/* здесь мы видим стандартный для котлина синтаксис определения
* класса вместе с параметрами конструктора и свойствами --
* в данном случае connection будет и в конструкторе и в поле.
* Есть возможность определить и множественный конструктор
* см https://kotlinlang.org/docs/reference/classes.html#constructors
* И, наконец, мы видим что класс реализует интерфейс Connection, все методы
* которого делегируются к переданному в конструктор экземпляру Connection-а.
* При желании их конечно можно переопределить в теле класса */
class ConnectionWrapper(val connection:Connection) : Connection by connection
У этого синтаксиса есть ряд ограничений. Например, инстанс для делегирования должен быть известен до вызова конструктора.
Второй тип делегирования — это delegated properties. Позволяет определить объект с методами get (и set для var), к которым будет осуществляться делегирование доступа при обращении к свойству объекта.
class Foo {
/* это делегат из стандартной библиотеки,
позволяет отложить инициализацию поля
до первого обращения к нему */
private val someProeprty by lazy { HavyType() }
}
Generics
Создатели котлина несколько улучшили Java-дженерики. Из-за совместимости с джавой не всё получилось как хотелось бы, но им удалось исправить много неприятных моментов, которые не учли их предшественники при работе над Java 5.
Деструктуризация
val (first, second) = someFunc()
Чтобы такой код заработал, возвращаемое значение из someFunc() должно быть типа, у которого есть (можно extension) методы component1(), component2():
class Foo {
fun component1():String = "test"
fun component2():Int = 10
}
fun someFunc():Foo = Foo()
// или так, to -- в это такой infix extension-метод определенный
// для Any, который создает экземпляр класса Pair, метод hashMapOf
// в свою очередь принимает vararg параметр таких пар
val map = hashMapOf(1 to "test")
for ((id, name) in map) {
// такой синтаксис возможен, потому что для Map-а определен метод iterator()
// возвращающий набор Map.Entry, а для него в свою очередь определены два
// extension-метода component1() и component2()
}
Data-классы
Сахар компилятора для создания бинов:
data class Bean(val a:String, val b:Int)
Создает бин с полями + автогенерирует equals+hashCode+toString()+componentN из раздела выше, что позволяет писать такой код:
fun someFunc():Bean = Bean("test", 10)
val (a, b) = someFunc()
Полезная вещь, но о нюансах см. пункт «О грустном».
Стандартная библиотека
Конечно, нельзя не упомянуть и стандартную библиотеку. Так как котлин нацелен в первую очередь на работу вместе с Java, то и целиком своей стандартной библиотеки у него нет. Большая часть стандартной библиотеки Kotlin нацелена на улучшение и исправление библиотеки старшего брата — Java. Однако, это тема для другой большой статьи.
О грустном
Вы могли подумать, что это идеальный продукт, но нет, есть и неприятные моменты:
IDE
Над плагином еще работать и работать, периодически выдает эксепшены, плохо умеет в toString() в дебаге, а так же любит промахиваться по ссылке на исходник, иногда (видимо из за особенностей инлайна) путает где поставлен брэкпоинт и тому подобные проблемы. Это всё конечно со временем наверняка поправят, но сейчас мы имеем именно это.
Data-классы
Надо признать, что идея была хорошая, но в данный момент есть масса ограничений , наложенных на этот тип классов, что позволяет их использовать в сильно более ограниченном числе кейзов, нежели хотелось бы. Создатели языка обещают поработать над решением этой проблемы, но пока так.
Некоторая неряшливость
Конечно, неряшливость в первую очередь в головах, но краткость синтаксиса иногда играет злую шутку, и местами код выглядит неважно. Возможно, наличие стайл-гайда несколько эту проблему исправило бы, но пока иногда приходится постараться, чтобы не только хорошо работало, но и красиво выглядело. Особенно на мой субъективный взгляд страшно выглядят get, set для свойств.
В заключение
Одной статьёй невозможно охватить все особенности и аспекты языка, но я и не пытался. Моей задачей было познакомить с языком, может быть обратить на него внимание. Тот, кто заинтересовался, сможет найти больше в документации, посмотреть исходники, попробовать, задать вопрос. Сложно предсказать популярность этого языка, но уже сейчас видно, что такого продукта многие ждали, проекты на котлине появляются как грибы, а после релиза частота их появления увеличится еще. По моему впечатлению, языка хорошо сбалансирован и продуман — во время написания кода, складывается ощущение, что всё на своем месте. Если вы используете jvm или любой другой язык со сборкой мусора, есть смысл обратить внимание на котлин. Лично для меня, котлин — это тот инструмент, которого я долго ждал и теперь не представляю, как мог бы обходиться без него.
Комментарии (40)
kosmonaFFFt
20.02.2016 12:22Уже пару раз в статьях про Kotlin его пытались сравнить со Scala, а как насчет Groovy? По моему, Kotlin и Groovy ближе друг к другу, чем Kotlin и Scala.
fogone
20.02.2016 12:40+3Груви — это всё-таки динамический язык, из за этого с котлином их не часто сравнивают, а чаще со скала. Честно говоря, мне не очень нравится дизайн груви, это коненчно субъективно, но всё же. У меня на нем не получается внятно "выразить мысль", часто спотыкаешься о какие-то неприятные мелочи.
kosmonaFFFt
20.02.2016 12:51Groovy умеет и в статическую типизацию тоже, просто надо объявлять типы. И если взять Groovy со статической типизацией, то он становится очень похож на Kotlin (или Kotlin на него).
fogone
20.02.2016 13:00У груви есть "строгий режим", да. Но он создает массу ограничений и видно, что приделан сбоку к языку. К тому же я не уверен, но вроде этот режим включается только на уровне каждого отдельного класса, не на уровне компиляции, которая у груви насколько я помню динамическая.
Throwable
22.02.2016 13:16+3Достаточно посмотреть доклады Евгения Борисова и Баруха Садогруского про Groovy, чтобы понять чтО это за язык. При кажущемся простом и логичном выражении мысли постоянно спотыкаешься о всевозможные грабли.
Кроме того статическая типизация там пятым колесом — все динамически типизировано. А динамика меня угнетает (даже с умной Idea).kosmonaFFFt
24.02.2016 07:05А можно где-нибудь эти доклады (или выжимку из них) не посмотреть, а почитать?
Throwable
24.02.2016 11:55Ну, можно попробовать закантактироваться с самими авторами, чтобы прислали или залили куда-нибудь свои презентации. Если Вы пишите на Груви, то посмотреть их тоже не помешает.
Zeliret
20.02.2016 12:24+1Что-то меня пугает синтаксис Конлина. Обилие двоеточий, какие-то стрелочки. Вроде даже символ @ видел в доке. Еще немного и получится руби. Который меня своим синтаксисом в свое время и отвернул от себя в пользу питона :)
Zeliret
20.02.2016 12:36+1Я бы тоже хотел узнать, чем отличается Котлин от Груви, т.к. судя по доке Груви, — он умеет почти все из современных штук-дрюк. ;D
fogone
20.02.2016 12:44fun passTen(func: (Int)->Int ): ()->Int { return { func(10) } }
Вот такой синтаксис действительно кажется плохо читаемым, но по факту я никаких проблем с "распаршеванием" таких выражений не испытываю. Наверное сказывается некоторая практика, но всё же.Gorthauer87
20.02.2016 12:50+4Почти такой же синтаксис сейчас в swift'е и относительно похожий в Rust'е. Это уже своего рода тенденция, как в свое время Java и C# были похожи на С++ и друг на друга. Видимо тут сказывается накопленый опыт в разработке новых языков программирования. Короче говоря, мозг тут придется один раз сломать, но зато после этого ему будут доступны для понимания, по крайней мере, сразу 3 языка программирования.
К тому же в них ещё много общего есть.fogone
20.02.2016 12:57Да, в этом смысле котлин хорош тем, что не пытается придумать какой-то свой синтаксис, а максимально использует распространенные способы записи — это сильно упрощает вход, большинство синтаксиса интуитивно понятно для того, кто имел дело с си-подобными языками.
nile1
20.02.2016 15:39+2Ну как минимум в Kotlin зачем-то ввели конструкцию fun вместо распространенных def (Python, Ruby, Scala).
С другой стороны, в Swift тоже своя конструкция func, а в Rust так вообще fn :)
Zeliret
20.02.2016 14:07+2Меня всегда смущало расположение типа после имени. Тут ради этого применяется еще и дополнительный символ — двоеточие, чтобы как-то разделить. Когда тип указывается до имени, то двоеточия не нужно, читабельность выше. Ты сразу понимаешь с чем работаешь, а потом уже смотришь на имя. А тут ты смотришь на имя, которое по сути вторично, а потом уже понимаешь, какого типа эта переменная, а какой тип возвращает функция. Я понимаю, что дело привычки, но не могу понять, почему так делают :)
Вот на примере того же Груви, я вот щас полистал доку их. Там есть все то же самое, что в Котлине. Но синтаксис кажется чище и менее многословным. Объявление конструктора в продолжении названия класса — для меня вообще дикость :) Т.е. ты перечисляешь в шапке класса его поля в одну строку. В Груви ты просто опускаешь конструктор, но описываешь поля в теле класса, и можешь инициализировать объект, просто передав туда именованные параметры в виде ключ-значение.voddan
20.02.2016 16:04Подобные проблемы с читабельностью обычно решаются нормальным форматированием. Например поля класса в Котлине никто в одну строчку не пишет, разбивают на несколько. Код в статьях призван просто демонстрировать принципы, и не всегда похож на "промышленный".
Zeliret
20.02.2016 16:27Но все равно шапка класса получается перегруженной. Там итак могут находиться родительский класс, интерфейсы/трейты и т.д., а тут еще и весь конструктор. Сомнительное решение, но я сужу лишь со своей колокольни.
voddan
20.02.2016 17:22На практике проблем не возникает. Наоборот, библиотека котлина одна из немногих, исходный код которой приятно читать.
Говорю за себя, но Котлин нужно пробовать. Он как хорошие кроссовки — и подошва удобная, и сами не слишком тяжелые. А всевозможные теоретические аргументы за четыре года успели обсудиться, и тот дизай который есть сейчас уже доказал свою оправданность на реальных больших проектах.Zeliret
20.02.2016 17:27При всем уважении, но на чистой java куда больше реальных больших проектов, но это не значит, что текущее состояние языка идеально за счет этого фактора :P
Но я как-нибудь попробую, при случае, чтобы все же не «оценивать фильм по чужим рецензиям» :)
burjui
20.02.2016 23:56+2По мне, так имена несут основную смысловую нагрузку в коде, а типы — это скорее инструменты. Вы можете сделать стул из дерева или из алюминия, но главным будет всё-таки назначение предмета, а не материалы, из которых он сделан. В данной аналогии стул — это имя сущности, а дерево и алюминий — типы (скажем, Int и Double).
Вот вам наглядный пример в коде:
import A.B import A.C sealed class A<T> { class B<T>(val c: A<T>, val d: A<T>): A<T>() { override fun toString(): String = "($c $d)" } class C<T>(val e: T): A<T>() { override fun toString(): String = "$e" } } fun main(args: Array<String>) { val f = B(C(1), B(C(2), C(3))) println("$f") }
Очевидно, не правда ли? Куда проще, чем:
import BinaryTree.Node import BinaryTree.Leaf sealed class BinaryTree<T> { class Node<T>(val left: BinaryTree<T>, val right: BinaryTree<T>): BinaryTree<T>() { override fun toString(): String = "($left $right)" } class Leaf<T>(val value: T): BinaryTree<T>() { override fun toString(): String = "$value" } } fun main(args: Array<String>) { val tree = Node(Leaf(1), Node(Leaf(2), Leaf(3))) println("$tree") }
Zeliret
21.02.2016 01:21-1А как вы будете делать стул, если вам не важен материал? С металом одна технология работы, а с деревом другая. Метод `add(List items)` сразу говорит нам, что мы добавляем список. Даже не нужно вникать в название параметра. Уже все ясно.
Но дело вкуса, конечно. Не зря же полно популярных языков с обоими стилями.
udalov
21.02.2016 01:10+6Когда тип указывается до имени, то двоеточия не нужно, читабельность выше. Ты сразу понимаешь с чем работаешь, а потом уже смотришь на имя.
Но ведь… Разве имя не более точно указывает, с чем работаешь? :)
Синтаксис с типом после имени более удобен тем, что его можно опускать, если в языке есть вывод типов. Опустить тип, который указан перед именем переменной, получается как-то не очень красиво.Zeliret
21.02.2016 13:00Почти везде, где есть вывод типов, используются ключевые слова, вроде def, let, var. В случае Котлина получается val в начале, а тип — в конце. А так был бы только тип вначале :) Целое слово сэкономили. В случае с Груви, мы пишем def, когда тип выводимый, а если нужно строго привести, то можем явно указать тип на месте слова def. Такая запись лично мне кажется более логичной.
zagayevskiy
21.02.2016 21:28Экономии там нет, т.к. идеологи Котлина хотят final везде, где только можно. Иммутабельность рулит. И котлин решает это введением var и val.
final int size = list.size();
val size = list.size();
nile1
Понятно, что в официальном блоге JetBrains такого прямого противопоставления не будет, но от коммьюнити хотелось бы четкого списка пунктов «Почему Kotlin, а не Scala».
Насколько я понимаю, в Scala недостаточно хорошо продумана обратная совместимость — как между версиями самой Scala, так и интероперабельность из Java в Scala, и наоборот. Например, те же properties в Scala не используют геттеры и сеттеры по умолчанию, а для совместимости нужно использовать какие-то дополнительные аннотации. Как на уровне байткода будут выглядеть геттеры и сеттеры в Kotlin?
С функциональщиной тоже порой коммьюнити Scala несет слишком далеко — невозможно разобрать, что все эти хитрые операторы и комбинации на самом деле означают. Как итог — высокий порог вхождения.
Как со всем этим делом в Kotlin? Правильно ли я понимаю, что стараются устранить недостатки Scala, и это и есть основная киллер-фича?
nile1
UPD. На официальном сайте есть краткое сравнение: https://kotlinlang.org/docs/reference/comparison-to-scala.html
fogone
Основная киллер-фича котлина, что они не пытаются делать киллер-фич, а нацелены на разработку простого и удобного инструмента для реальной разработки. Конечно, можно пытаться сравнивать скалу и котлин, и в каждом посте обязательно про это бывает, но лично я не вижу большого смысла про это много писать — в статье по этому поводу раздел "Простой и совместимый". Это же и главные отличия от скалы, на мой взгляд.
lgorSL
Можно написать пару маленьких программок и декомпилировать.
геттеры и сеттеры будут реализованы как методы
getVarName()
иsetVarName(...)
Но мне синтаксис Scala кажется более простым. В kotlin больше сущностей и ключевых слов.
Например, для переопределения
+=
в scala достаточно создать метод с очевидным названием "+=", причём нет никаких ограничений на тип и количество аргументов — это самый обычный метод, а не какая-то дополнительная сущность.В kotlin придётся написать в стиле
operator fun plusAssign
, что не совсем интуитивно (приходится вспоминать, что и как называется), и есть ограничения — метод не может возвращать значение.Причём в kotlin можно переопределить только небольшой список операторов, а в scala свободы побольше (например, можно определить := или ++=).
На самом деле, scala-метод типа += будет создан с именем "$plus$eq", которое не очень удобно вызывать из java. Было бы идеально, если бы в kotlin стало можно давать методам имена типа "+", которые на самом деле отображались бы в "plus" и.т.п.
Или, например, синтаксис объявления метода:
В scala:
def methodName(...) = {...}
В kotlin возможны два варианта — как в scala (со знаком =) и как в java (без него), но эти два способа объявления неэквивалентны друг другу и работают немного по-разному, я однажды кучу времени потратил на поиск такой "особенности" в коде.
Null-safety: интересная штука, но на практике во все публичные методы добавляются проверки для каждого аргумента на null. Мне кажется несколько избыточным — и есть подозрение, что это негативно влияет на производительность. (Там не просто проверка, а вызов статического метода с передачей объекта-параметра и его имени как строки, чтобы можно было кинуть понятное исключение)
Что понравилось в kotlin:
1) inline. Никаких созданий объектов, просто подстановка тела функции.
2) extensions. Тоже удобная штука — и никаких потерь производительности.
В Scala похожая функциональность сделана через implicit-преобразования, из-за чего при вызове создаётся ещё один объект.
senia
В scala есть extends AnyVal. Для таких классов тоже не создается экземпляров, если это возможно.
В Linker собираются автоматически добавлять extends AnyVal к классам.
vektory79
Даже это делать не надо. В IDEA есть отдельное окно, показывающее, во что превратиться написанный на Kotlin'е исходник. Очень удобно и наглядно демонстрирует качество компилятора...
Это может и выглядит несколько нагружено, если смотреть в байткоде. Но на деле JVM отлично знает как это оптимизировать. И в рантайме никаких издержек выявить не удалось. Более того, если вы попробуете посмотреть, что получется уже на уровне машинного кода после JIT, то навряд ли найдёте эти проверки в явном виде. Где-то про это даже доклад был на одной из конференций.
gagoman
А могли бы подсказать где это окно?
Спасибо
udalov
Tools -> Kotlin -> Show Kotlin bytecode. Или Ctrl+Shift+A (Cmd+Shift+A) -> "bytecode"
gagoman
Спасибо!
fogone
Я понимаю мотивацию дизайнера языка, который принял решение о таком переопределении операторов. Максимального ограничение возможностей переопределения операторов — это максимальная изоляция программиста от ошибок связанных с такие переопределением, однако минимальнонеобходимая возможность переопределения есть. К тому же это позволяет сохранить достаточно однообразный код.
По поводу объявления метода, то в котлине
идентичны. Однако вот такой код
не скомпилируется, потому что возвращаемое значение будет ()->Int
Единственное различие которое я знаю: если использовать expression вариант, то возвращаемое значение можно не писать, а в блочном варианте компилятор такое уже не пропустит.
lgorSL
Я подразумевал следующее:
Чтобы вывести "surprise", придётся написать test3()(). Вариант вызова test3() тоже нормально компилируется, только сработает не так, как ожидалось — добавление "лишних" скобочек кардинально меняет логику программы.
Из-за этих граблей переход со скалы на котлин оказался немного болезненным — иногда "по привычке" в объявлении какого-нибудь метода пишу знак равенства, а потом приходится искать ошибки.
fogone
Да, я отлично понял о чем вы. Но это актуально только для совпадения двух не очень часто встречающихся факторов: когда функция ничего не возвращает, а вы много лет писали на скала :-)
zagayevskiy
Не понимаю, зачем нужно переопределять операторы. В С++ этим увлекался, но там есть смысл это делать. А в языках типа джавы — зачем?