Kotlin: джависты, завидуйте

Около года назад, в подкасте радио-т я впервые услышал о инициативе JetBrains, новом языке программирования kotlin. С тех пор внимательно слежу за его развитием.
Они позиционируют котлин как "better java" и, надо сказать, у это получается. Это статический типизированный, язык со вшитой nullable-проверкой Так же он поддерживает функции высшего порядка(замыкания), extension functions и trait. Может немного напоминать scala - но, достаточно далеко от неё.


Впринципе, языков, в том числе и jvm-based достаточно много, но я обратил внимание именно на kotlin, потому что:

  • java - слишком проста. Как ассемблер. Её скудность синтаксиса вгоняет меня в уныние. Я хочу решать конкретные задачи, а не писать километры кода.
  • scala - слишком сложна. Она не только вносит свою систему типов, но и вводит непонятки в стиль написания кода. Те, кто пробовал её знают, что можно писать на скале как scala-style так и java-style.
  • scala - оооочень долго собирается. Не может язык, который призван решать практические задачи столько компилироваться. Или ради этого нужно поднимать свой кластер? Есть даже шутка: "Почему скала так долго собирается? В это время Одерский майнит биткоины" :D
  • groovy - классный, но медленный. И динамически типизированный. И почти не работает под андроидом
  • kotlin не тащит за собой всю систему типов - их небольшой рантайм расширяет стандартные коллекции и классы.

Код!

Пока мне больше видится использование kotlin на android (далее все примеры из мира андроид разработки). Всякие aop, groovy упрощяют жизнь в java-мире на серверах.

Доступ к компонентам интерфейса

При обращении к свойству происходит вычисление значения функции


class RiddleActivity: FragmentActivity() {
val answer: TextView? get() = findView<TextView>(R.id.riddleAnswer)
val question: TextView? get() = findView<TextView>(R.id.riddleQuestion)
val nextQuestion: Button? get() = findView<Button>(R.id.riddleNextQuestion)
. . . .
}

Регистрируем обработчики


private fun bind() {
. . .
array(barCounter, findView(R.id.riddleConfigureCategories)).forEach { v ->
v?.setOnClickListener {
CategoriesChooserFragment({ isChanged ->
if (isChanged) currentOffset = 0
}).show(getSupportFragmentManager(), "categories")
}
}
}

При этом добавляем методы ко всем классам View (extension functions)


public fun View.findView<T: View>(id: Int): T? = findViewById(id) as? T

public fun onClickListener(action: (View?) -> Unit): OnClickListener {
return object : OnClickListener {
override fun onClick(v: View) {
action(v)
}
}
}

public fun View.setOnClickListener(action: (View?) -> Unit): Unit {
setOnClickListener(onClickListener(action))
}

Просто описываем класс


open class Riddle(
var id: Int = 0,
var number: Int = 0,
var text: String = "",
var answer: String = "",
val solved: Boolean = false,
val purchased: Date? = null,
val favorited: Date? = null
) {
}

Вытаскиваем данные в DAO


class RiddleDAO(val context: Context) {
. . . .

private fun transaction<T>(action: (database: SQLiteDatabase) -> T): T {
var result: T
open()
val db = this.database
if (db == null) throw SQLException("No database")
db.beginTransaction()
try {
result = action(db)
db.setTransactionSuccessful()
} finally {
db.endTransaction()
}
close()
return result
}

fun setCategoryEnabled(categoryId: Int, isEnabled: Boolean) {
transaction {
val cv = ContentValues()
cv.put(FIELD_ENABLED, if (isEnabled) 1 else 0)
database?.update(TABLE_CATEGORIES, cv, "id = " + categoryId, null)
}
}

fun count(onlyEnabled: Boolean = false): Int {
return transaction {
val query = "SELECT COUNT(*) FROM ${TABLE_RIDDLES} WHERE ${if (!onlyEnabled) "1=1" else ONLY_ENABLED_CONDITION}"
val cursor = database?.rawQuery(query, null)
cursor?.moveToFirst()
cursor?.getInt(0) ?: 0
}
}
}

Иногда код выглядит очень эмоциональным!!


private fun getView(inflater: LayoutInflater?): View {
val view = inflater!!.inflate(R.layout.categories, null)!!

java конструкции выглядят более человечными

Если не оборачивать все стандартные вызовы в коллбэки, выглядит все равно не плохо. Пример с созданием DialogFragment


override fun onCreateDialog(savedInstanceState: Bundle?): Dialog? =
AlertDialog.Builder(getActivity()!!)
.setView(getView(getActivity()!!.getLayoutInflater()))
.setTitle("Select categories")
.setNeutralButton("Select all", object: DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, which: Int) {
}
})
.setPositiveButton("Ok", object : DialogInterface.OnClickListener {
override fun onClick(dialog: DialogInterface, which: Int) {
}
})
.create()

Унифицированный доступ к настройкам

Сам определяет тип по дефолтному аргументу, цепляется к любому Context (Activity, Service, ..)


public fun Context.loadPref<T>(key: String, default: T): T {
val sp = getSharedPreferences(getClass().getCanonicalName(), 0)
return when (default) {
is Int -> sp.getInt(key, default)
is Boolean -> sp.getBoolean(key, default)
is String -> sp.getString(key, default)
is Float -> sp.getFloat(key, default)
is Long -> sp.getLong(key, default)
else -> default
} as T
}

....

val lastVersion = loadPref("version", 0);

Скачать бесплатно и без смс

Само приложние Riddles, куски кода которого я разместил можно скачать с Google Play

Надо добавить, что язык ещё очень не стабильный до первого релиза. Спустя месяц, например, приложение уже не собирается :)