Я впервые встретился с Kotlin 17 июня этого года, и с тех пор я не использовал другой язык. (То есть почти!)
Автор Юрай Мичко (19), разработчик программного обеспечения
Все началось за несколько недель до этого на HackPrague, хакатоне в Праге, который я открыл благодаря StarLift. Я мало что знал о мероприятии, но мы с друзьями составили красивое сопроводительное письмо в формате PDF, и, к счастью, мы вошли.
Это должен был быть наш первый хакатон.
Но как это связано с Котлином? Что ж, был спонсорский приз «Давайте программу в Котлине!» на хакатоне, побуждая участников использовать язык новорожденных.
Моим первым впечатлением было то, что за ужасный язык, должно быть, нужно вознаграждать людей за его использование? Но судя по тому, что я слышал, он должен был быть инновационным, но при этом иметь возможность взаимодействия со старой доброй Java.
Конечно, у нас не было времени выучить новый язык во время хакатона, но мы заняли 2-е место в категории «Самый инновационный UX для обнаружения мобильных событий» и, мотивированные победой, посетили еще один хакатон в Праге. буквально через неделю. На второй день написания кода мы поняли, что наша концепция не является выигрышной, и захотели сделать наш проект более инновационным. В то время мой Github включал несколько классов, написанных на Kotlin, и я получил первое представление о том, что может быть Kotlin - и что, возможно, это может быть не так уж и плохо.
(Сторона нет: это не помогло нам победить.)
Итак, я начал больше узнавать о Котлине в следующие выходные, и это оказалось опытом, изменившим мою жизнь. Чем больше я узнавал, тем больше меня это увлекало. И я надеюсь, что смогу мотивировать вас тоже попробовать его, так что давайте взглянем на некоторые из его замечательных функций.
Не скучный язык
Программистам на Java очень выгодно, чтобы все было объявлено с типом, известным во время компиляции. Недостатком является написание скучных частей кода, то есть то, что может написать обезьяна, получая только строку кода, даже не зная контекста.
String s = "Hello World!";
BufferedStreamReader bsr = new BufferedStreamReader(stream);
В Kotlin то же самое можно сделать без скучного кода. Например, разработчики Kotlin считают объявления типов, ключевые слова new
и точки с запятой скучными, поэтому эквивалент в Kotlin:
val s = "Hello World!"
val bsr = BufferedStreamReader(stream)
Конечно, вы можете переопределить предполагаемый тип или предоставить его, если он не может быть выведен.
val pet: Animal = Dog()
val pet2 = Dog() as Animal // downcasting or upcasting
Всякий раз, когда вы указываете val
, переменная является окончательной. Для изменяемых переменных используйте var
.
Нулевая безопасность
В Kotlin каждый тип может содержать нулевые значения (String?
) или не может (String
). Присвойте возможно нулевое значение переменной, не принимающей нулевые значения, и компилятор пожалуется. Также не допускается вызов метода для типа, допускающего значение NULL. Так как же нам разобраться? Все следующие примеры дают одинаковый результат.
// This is basically what one would write in Java style
fun whoIs(user: User?): String? {
if (user != null) {
return user.name
} else {
return null
}
}
// But the `if` block is also an expression in Kotlin
fun whoIs(user: User?): String? {
return if (user != null) user.name else null
}
// The ?. operator will invoke the method only if the object is not null,
// otherwise the whole expression evaluates to null
fun whoIs(user: User?): String? {
return user?.name
}
// Finally, using a short syntax of methods (the inferred return type is still String?)
fun whoIs(user: User?) = user?.name
// We can supply default values with the Elvis operator ?: that will evaluate
// to the right side if and only if the expression on the left is null
// Note that in this particular case, the compiler infers the return type to by non-null `String`
fun whoIs(user: User?) = user?.name ?: "no one"
Функции расширения
Некоторые языки поддерживают расширение существующих классов путем определения их членов (полей или методов) без изменения определения классов вообще. Библиотеки, как правило, определяют множество функций расширения для общих объектов, и методы в конечном итоге сталкиваются, поскольку в классе не может быть двух методов с одинаковой сигнатурой. Однако в Котлине есть все.
Во-первых, важно понять, как работают функции расширения Kotlin и как их можно скомпилировать для Java. Рассмотрим следующее определение:
fun Boolean.thenRun(block: () -> Unit): Boolean {
if (this) {
block.invoke()
}
return this
}
У метода есть один аргумент - лямбда без параметров, возвращающая Unit
(эквивалент void
в Java). Если получатель функции (то есть логическое значение) истинно, вызывается лямбда. После этого возвращается исходное логическое значение.
Давайте сохраним код в MyFile.kt
, скомпилируем его и декомпилируем полученный MyFileKt.class
обратно в Java. Результат примерно такой (я удалил неважные части):
public final class MyFileKt {
public static final boolean thenRun(boolean $receiver, @NotNull Function0 block) {
Intrinsics.checkParameterIsNotNull(block, "block");
if($receiver) {
block.invoke();
}
return $receiver;
}
}
Как видите, Kotlin решает проблему, определяя общедоступный статический метод вместо того, чтобы принимать логическое значение в качестве первого аргумента. Удивительный! У нас может быть столько библиотек, сколько мы хотим, и столкновения членов не произойдет, поскольку вам всегда нужно импортировать правильную.
Несколько заметок здесь:
- Тип Kotlin
Boolean
изменился наprimitive boolean
, поскольку компилятор знает, что он не может содержать нулевые значения. - Мы определили метод непосредственно в файле, но не как член класса. Kotlin также поддерживает функции расширения в качестве членов класса.
- Как вы могли заметить, в Котлине обычно используются
public
иfinal
, если не указано иное.
Теперь подумайте, какое применение может иметь эта функция. Взгляните на следующую логику, написанную на Java:
public class MyClass {
...
public boolean doSomething() {
boolean isSuccess = doTheTask(); // returns true on success
if (isSuccess) {
System.out.println("OK");
}
return isSuccess;
}
}
Довольно просто. Запускаем задачу, сохраняем ее результат. В случае положительного результата мы запускаем другой блок кода (в данном случае вывод на консоль). Затем возвращаем исходное значение. А теперь посмотрим, что предлагает Котлин:
fun doSomething(): Boolean {
return doTheTask().thenRun {
println("OK")
}
}
// or even better
fun doSomething() = doTheTask().thenRun { println("OK") }
Мало того, что код намного короче, мы полностью избавились от переменной!
Обратите внимание, что если последний аргумент функции - лямбда, вы можете вывести его из круглых скобок, и вы можете опустить их, если круглые скобки пусты. Kotlin имеет множество встроенных функций расширения, которые вам не нужно писать.
Нет шаблонного кода
Код на Котлине:
data class Human(var name: String?, private val birth: Date) : Mammal(birth)
Код, декомпилированный в Java, следует ниже. Я снова удалил неинтересные части.
public final class Human extends Mammal {
@Nullable
private String name;
private final Date birth;
public Human(@Nullable String name, @NotNull Date birth) {
Intrinsics.checkParameterIsNotNull(birth, "birth");
super(birth);
this.name = name;
this.birth = birth;
}
@Nullable
public final String getName() {
return this.name;
}
public final void setName(@Nullable String var1) {
this.name = var1;
}
@Nullable
public final String component1() {
return this.name;
}
private final Date component2() {
return this.birth;
}
@NotNull
public final Human copy(@Nullable String name, @NotNull Date birth) {
Intrinsics.checkParameterIsNotNull(birth, "birth");
return new Human(name, birth);
}
public String toString() {
return "Human(name=" + this.name + ", birth=" + this.birth + ")";
}
public int hashCode() {
return (this.name != null?this.name.hashCode():0) * 31 + (this.birth != null?this.birth.hashCode():0);
}
public boolean equals(Object var1) {
if(this != var1) {
if(var1 instanceof Human) {
Human var2 = (Human)var1;
if(Intrinsics.areEqual(this.name, var2.name) && Intrinsics.areEqual(this.birth, var2.birth)) {
return true;
}
}
return false;
} else {
return true;
}
}
}
Думаю, вы поняли, что Kotlin лаконичен. Намного более лаконично.
Свойства и делегированные свойства
Когда есть методы getSomething()
и setSomething(value)
, Kotlin считает их свойством, и вы можете получить к нему доступ, просто написав obj.something
и obj.something = value
. Когда вы создаете класс со свойством, например, в следующем примере, он компилируется в класс с частным свойством с соответствующими геттерами и сеттерами. Именно это и произошло в предыдущем примере. Конечно, вы можете все изменить.
open class HeyWorld(val abc: String) { // not final class
// `abc` is a constructor argument and an immutable property with a setter
var foo = "bar" // public getter and setter
private val now = Date() // private getter and no setter (because of `val`)
protected var amount: Int? = null // protected property initialized to null
// here we define custom getter; field is a special keyword in this scope
get() = field ?: 0 // returns the value if not null, otherwise returns 0
private set // setter is private
open var abracadabra: Boolean? = true // not final field (unlike the rest)
set(value) { // value is of type Boolean?
field = if (value != null)
// but we negate it as it has been smart-casted to Boolean
!value
else null
// the same could be achieved by writing
// field = value?.not()
}
}
Делегированное свойство означает, что вы делегируете ответственность за принятие и предоставление ценности кому-то (или чему-то) еще.
class Example {
var p: String by Delegate()
}
Для объекта Delegate
должны быть определены определенные методы getValue
и setValue
, но давайте снова сосредоточимся на удобстве использования.
// lazy evaluation - the value is evaluated only when it's first accessed
val lazyValue by lazy {
print("The property is being evaluated for the first time!")
"Hello World!"
}
// managing more properties by one object
class Person(val map: MutableMap<String, Any?>) {
var name: String by map
var age: Int by map
}
Вывод
Котлин обладает множеством волшебных свойств. Вначале меня поразило то, что все компилируется для Java и, следовательно, должно соответствовать ее ограничениям. И действительно, каждой магии здесь есть объяснение.
В статье многое осталось необъяснимым, и вам может быть интересно, действительно ли язык работает так, как ожидалось, и есть ли какие-либо недостатки. Что ж, вы можете сами прочитать документы или поверить мне, когда я скажу, что это потрясающе. Удачи и удачного кодирования!
- Справочник Котлина
- Лучшие возможности Котлина!
- Десять функций Kotlin для ускорения разработки под Android
Вы молодой разработчик из Чехии или Словакии? Хотели бы вы начать свою карьеру в стартапе в США или ЕС? Свяжитесь с нами прямо сейчас через наш сайт, Facebook или Twitter!