* На самом деле меньше, но все равно достаточно 🙂
Ребята из JetBrains сделали очень много крутых вещей, среди которых Kotlin — молодой, но очень перспективный язык программирования под JVM и Android. В этой статье я покажу, чем этот язык может заинтересовать Android-разработчика и заставить его навсегда позабыть про Java.
Java сдает позиции
Стоит признать, что Java в последнее время развивается не очень активно. Новые фишки добавляются в нее со скрипом. Отчасти это вызвано тем, что «большая» Java, та, что еще называется Java EE, тащит за собой огромный багаж проектов, которые необходимо беречь при апгрейде на новую версию. Из-за этого появляются новые JVM-совместимые языки программирования, которые, с одной стороны, не обременены обратной совместимостью и могут развиваться очень динамично, а с другой — могут работать со всей огромной кодовой базой Java: библиотеками, фреймворками, плагинами. И Kotlin — один из таких языков.
Эта статья не научит тебя азам и не покажет тебе Hello world на Kotlin. Она здесь для того, чтобы заинтересовать тебя, программиста под Android, новым инструментом разработки — невероятно гибким, расширяемым и простым.
INFO
Язык Java назван в честь марки кофе, которая получила имя от острова Ява в Индонезии. Шутка ли, что существует и остров Котлин? Он находится в Финском заливе около Санкт-Петербурга, и на нем располагается город Кронштадт.
Лаконичность
Kotlin очень бережет время программиста и старается сделать так, чтобы тот долбил по клавишам как можно реже. Взгляни на этот пример инициализации пары переменных на Kotlin:
var userNames = ArrayList<String>()
val admin = "ADMIN"
Ключевые слова var
и val
указывают, что ты хочешь объявить переменную или константу. При этом тебе не нужно задавать их тип самостоятельно — в большинстве случаев Kotlin сам может определить тип переменной по коду. А теперь давай посмотрим на эквивалентный код на Java и найдем менее очевидные отличия:
ArrayList<String> userNames = new ArrayList<>();
final String admin = "ADMIN";
Во-первых, ты наконец-то можешь перестать писать ; в конце каждой строки, теперь это не обязательно. Во-вторых, ты избавляешься от ключевого слова new
перед созданием объекта.
Еще один отличный пример лаконичности Kotlin — это строковые шаблоны. Вспомни, как Java издевалась над тобой, когда ты хотел составить строку с какими-нибудь динамическими данными. Наверняка это выглядело примерно так:
// Плохой вариант
String message = "Hello " + name + " " + lastName;
// Вариант получше
String message = String.format("Hello %1$S %2$S", name, lastName;
А теперь забудь это, ведь с механизмом строковых шаблонов на Kotlin ты можешь вставлять переменные прямо внутрь строки:
val message = "Hello $name $lastName"
Ты можешь использовать для составления строки даже вызов метода, если обернешь его в фигурные скобки. К примеру:
"Hello ${user.getName()}"
Теперь давай рассмотрим пример, непосредственно связанный с Android. Сможешь посчитать, сколько раз ты писал вызов findViewById(...)
? Я встречал проекты, в которых их было почти 400! Конечно, есть ButterKnife, который сделает все за тебя, но даже с ним тебе придется заводить отдельное поле под каждую вьюшку, чтобы навесить на него аннотацию @BindView
. В Kotlin все становится еще проще, если подключить стандартный плагин kotlin-android-extensions. Этот плагин позволяет, добавив всего одну строчку в секции import
, получить доступ ко всем вьюшкам, имеющим уникальный ID в XML-разметке. Смотри, как это работает.
Подготовим XML-разметку нашего будущего экрана со всего одной кнопкой, которую назовем pushMe.
// Файл activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<Button
android:text="Hi there"
android:id="@+id/pushMe"
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</FrameLayout>
Создадим новую активити с названием MainActivity и установим ей наш лейаут.
class MainActivity : Activity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
Немного о синтаксисе языка
В Kotlin нет аннотации @Override
, но есть ключевое слово override
. Слово extends
превратилось в двоеточие, а методы (функции) объявляются ключевым словом fun
.
ОK, теперь, чтобы получить доступ к нашей кнопке, просто добавим еще одну строку в секцию импортов:
// activity_main — имя файла с XML-разметкой
import kotlinx.android.synthetic.main.activity_main.*
И все, теперь мы можем работать с кнопкой:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_auth)
pushMe.setOnClickListener(
// Что-то делаем по клику
)
}
Еще одна фишка Kotlin, позволяющая писать меньше кода, — это лямбды. Вообще, лямбда-функции и функции высших порядков (функции, которые могут принимать функцию как параметр или возвращать ее) — это очень мощный инструмент, который заслуживает отдельной статьи, но в следующем примере я покажу тебе, как ты можешь их использовать в разработке под Android, даже не зная принципов работы.
Вот как обычно выглядит установка callback’а на клик по кнопке в Java:
pushMe.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(context, "Hi there!", Toast.LENGTH_SHORT).show();
}
});
Здесь создается новый анонимный класс, который реализует интерфейс View.OnClickListener
, и он сразу передается в качестве параметра в метод setOnClickListener(...)
. Заметь, что у интерфейса View.OnClickListener
есть всего один метод onClick(View view)
, который тебе нужно реализовать. Эта информация пригодится нам позже.
А вот как это выглядит в Kotlin:
pushMe.setOnClickListener {
Toast.makeText(context, "Hi there!", Toast.LENGTH_SHORT).show()
}
Код стал в три раза короче! Все, что ты видишь, от первой фигурной скобки до последней — это и есть лямбда-функция. Фокус заключается в том, что все анонимные классы, у которых есть только один метод, могут быть написаны в Kotlin как лямбды.
Последнее, о чем я хочу рассказать тебе, говоря про компактность кода в Kotlin, — это property (свойства) классов и автоматическая генерация методов. В каждой популярной IDE под Java есть функция генерации кода, такого как getter’ы, setter’ы, toString()
, equals()
, hashCode()
и прочие. Android Studio, к слову, умеет генерировать это все и еще немного больше (рис. 1).
Xakep #211. Когда «Окна» смотрят в тебя
Давай посмотрим на типичный код модельного класса:
public class User {
private String fullName;
public User(String fullName) {
this.fullName = fullName;
}
public String getFullName() {
return fullName;
}
public void setFullName(String fullName) {
this.fullName = fullName;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return fullName != null ? fullName.equals(user.fullName) : user.fullName == null;
}
@Override
public int hashCode() {
return fullName != null ? fullName.hashCode() : 0;
}
@Override
public String toString() {
return "User{" + "fullName='" + fullName + '\'' + '}';
}
}
У класса есть одно приватное поле, getter и setter под него, конструктор и еще набор стандартных переопределенных методов. Очень много кода генерируется IDE. А если такой код может генерировать IDE, это может сделать и компилятор. Взгляни, насколько проще выглядит аналогичный Kotlin-код:
data class User(var fullName : String)
Нет, это не опечатка, это действительно эквивалентный класс, и он уместился в одну строку. Давай я расскажу подробнее, что здесь происходит. Во-первых, конструктор класса объявляется в той же строке, что и сам класс. Во-вторых, все переданные в конструктор var-параметры будут полями этого класса. В-третьих, все неприватные поля класса автоматически получают свои getter- и setter-методы. Наконец, ключевое слово data
указывает компилятору сгенерировать методы equals()
, hashCode()
, toString()
и copy()
.
У методов getter и setter в Kotlin есть одна особенность. Если у класса есть одновременно и getter, и setter для какого-либо поля, то в Kotlin такое поле считается свойством (property) и для сокращения записи можно использовать следующий синтаксис:
// Обычный Java-синтаксис
user.setFullName("Agent Smith")
// Синтаксис Kotlin
user.fullName = "Agent Smith"
// Обычный Java-синтаксис
String fullName = user.getFullName()
// Синтаксис Kotlin
val fullName = user.fullName
Обрати внимание, что ни в одном из случаев мы не работали с полем User.fullName
напрямую, только через соответствующие getter -и setter-методы, хоть в случае с Kotlin это не так очевидно.
Ошибка на миллиард
Многие языки программирования пытаются по-своему решить ошибку на миллиард долларов, и Kotlin не исключение. Чтобы свести к минимуму возможность возникновения NullPointerException
, в системе типов Kotlin есть типы nullable и non-null. Различаются эти типы знаком вопроса после имени самого типа. Это очень похоже на аннотации @Nullable
и @NonNull
в Java, но в отличие от последней Kotlin очень строго бдит за работой с типами nullable и non-null. К примеру, компилятор не даст тебе записать null в переменную non-null типа и не даст выполнить никакую операцию, потенциально приводящую к NPE, без предварительной проверки на null:
var myString : String
myString = "Hi there!"
myString = null // Ошибка компилятора
// ? означает, что переменная может быть null
var iAmNull : String?
iAmNull = null // OK
// Ошибка компилятора, iAmNull может быть null
val len = iAmNull.length
// После проверки на null можно делать что угодно
if (iAmNull != null){
val len = iAmNull.length
}
Конечно, негуманно заставлять тебя писать такие громоздкие конструкции для проверки на null, поэтому в Kotlin есть несколько null-safe-операторов, сокращающих запись:
// Оператор ?. вернет или значение выражения справа от него
// Или null, если переменная — null
val len = myString.length // len1 == 9
val len = iAmNull?.length // len2 == null
// Конструкция if-else может возвращать значение
val len = if (iAmNull != null) iAmNull.length else -1 // len == -1
// Elvis-оператор ?:
// ?: вернет выражение слева, если оно не null,
// или выражение справа в противном случае
val len = iAmNull?.length ?: -1 // len == -1
Расширяемость
Программируя на Kotlin, ты смело можешь руководствоваться следующим принципом: если тебе чего-то не хватает — напиши это. Kotlin очень гибкий язык. Во многом за счет своих лямбд, функций высших порядков и функций расширения. Про первые две мы уже немного поговорили, теперь узнаем про последнюю. С помощью функции расширения (extension function) ты можешь добавить возможности уже существующим классам, даже стандартным, без изменения их кода. Представь, что тебе надоело писать эту длинную и зачастую одинаковую строчку Toast.makeText(context, "Hi there!", Toast.LENGTH_SHORT).show();
и ты хотел бы научить Activity делать часть работы самостоятельно. С функцией расширения в Kotlin это становится реальным:
fun Activity.toast(message: CharSequence) {
Toast.makeText(this, message, Toast.LENGTH_SHORT)
.show()
}
Ключевое слово fun
говорит о том, что дальше следует определение функции (метода, если тебе так привычнее). toast(message: CharSequence)
— это имя функции и ее аргументы. В фигурных скобках — тело функции, все как обычно. А необычно здесь то, что перед именем функции через точку указан класс Activity
. Это говорит компилятору, что эта функция теперь является методом Activity, и дает тебе право вызывать ее напрямую у любой Activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
toast("Hi there!")
}
Довольно неплохо! Но напоследок я покажу тебе кое-что покруче. Временами в программе возникает потребность пройтись по всем вьюшкам и что-то с ними сделать. Это выглядит примерно так:
int childCount = parentView.getChildCount();
for (int i = 0; i < childCount; i++) {
View child = parentView.getChildAt(i);
// Что-то сделать с child
}
Проблем с этим кодом и не было бы, если бы его не приходилось частенько дублировать. Как мы можем улучшить себе жизнь на Kotlin? Комбинацией функции расширения и лямбды!
inline fun ViewGroup.loopViews(action: (View) → Unit) {
for (i in 0..childCount){
action(getChildAt(i))
}
}
Да-а, такая конструкция новичку в Kotlin покажется чуждой и сложной. Но если разложить все по полочкам... Смотри, с конструкцией fun ViewGroup.loopViews
ты уже знаком — это функция расширения для стандартного класса ViewGroup
. Unit
— это аналог Void
из Java. (action: (View) → Unit)
говорит компилятору о том, что наша функция принимает на вход в качестве аргумента другую функцию (да-да, та самая функция высшего порядка!), у которой один аргумент типа View
и которая не возвращает ничего (Unit
), и имя ей — action
. В теле нашей основной функции мы проходим по всем своим вьюшкам и вызываем над каждой вьюшкой ту функцию, которую нам передали в качестве аргумента. Ключевое слово inline
тут нужно для оптимизации, чтобы не создавать новый объект action
на каждый его вызов, сборщику мусора потом будет проще.
ОK, разобрались, но что нам это дает? Это дает возможность писать такого рода код:
parentView.loopViews { /* что-то сделать с каждой вьюшкой */ }
// Например, закрасить все вьюшки черным цветом
parentView.loopViews { view → view.setBackgroundColor(Color.BLACK) }
Видишь? На Kotlin можно написать практически все, чего тебе не хватало на Java.
Как начать?
Хорошая новость: начать писать на Kotlin очень просто. Во-первых, нужно установить плагин Kotlin в Android Studio: Settings → Plugins → кнопка Browse Repositories → найти плагин Kotlin и установить его. Во-вторых, добавить поддержку Kotlin в свой проект: Help → Find Action → впиши в строке поиска kotlin → выбери пункт Configure Kotlin in Project, как показано на рис. 2, и дальше по инструкции. Студия сама добавит нужные строчки в конфиги build.gradle. В качестве бонуса в студии появится новая опция, позволяющая автоматически конвертировать существующие Java-классы в Kotlin: Code → Convert Java file to Kotlin file.
Вторая хорошая новость: тебе не обязательно переписывать весь свой проект на Kotlin. Добавив поддержку Kotlin, ты можешь писать новый код на этом языке, и он отлично будет работать с твоим кодом на Java. Весь твой Java-код останется доступен в Kotlin, все подключенные Java-библиотеки — тоже. Весь твой Kotlin-код будет доступен в Java (ну, почти весь).
Заключение
Я показал тебе только несколько крутых возможностей Kotlin, но за кадром осталось еще очень много интересного. Если тебе понравился новый язык и ты уже хочешь начать что-то делать самостоятельно, рекомендую заглянуть на официальный сайт Kotlin. Там в разделе Learn ты найдешь гайд по азам языка, а в разделе Try Online сможешь порешать задачки на Kotlin от элементарного до экспертного уровня. Удачи!